commit 240332c0d2cac7bcd42090475e6dda2c53cef90e Author: Lurkars Date: Thu Dec 7 18:16:26 2017 +0100 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0d366de --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2017 Lukas Haubaum + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..547d5d6 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Muffcast +An alternative to Chromecast working with every website with HTML5 Video/Audio elements by just playing HTML5 video/audio on other Firefox instance in full-screen. + +## Client +This is the client extension. Go to any website with HTML5 Video elements to play it on the **Muffcast Server** instance by clicking on a **Muffcast**-symbol next to it. + +### Requirements +- Firefox/Browser +- **Muffcast Server** running in same network +- Internet access + +### Firefox/Browser Setup +In Add-ons Preferences, define URL of the *Muffcast Server*-extension running on any device in your network on port 8128. + +###### Limitations +This only works for websites with HTML5 Video/Audio elements. This does not work in native applications. + +To work properly for websites with authentication (like Netflix), the browser on server side also needs valid session. This extension does not handle any authentication, so valid sessions are required. In short: manually login and save session before use. + +There are some websites that required further interactions before the HTML5 Video is loaded properly, e.g. to click a non-standard play button. Those sites do not work without special treatment in the server component. Please feel free to report issues with those sites for being included in server component code. + +This extension is developed and tested in Firefox 57. A port for other browsers like Chrome should be easy due to WebExtensions API, but is not warranted to work properly. + +Video quality is not part of the Media API and any websites handles this on it's own. So like authentication, to control playback quality, manually settings on server side are required. (Hopefully the automatic settings fit your needs, but e.g. on a Raspberry Pi to high quality can cause stuttering.) diff --git a/background/client.js b/background/client.js new file mode 100644 index 0000000..5d4d2f0 --- /dev/null +++ b/background/client.js @@ -0,0 +1,88 @@ +console.log("muffcast client v0.1"); + +var muffcastUrl = "http://localhost:8128"; +browser.storage.local.get("muffcast").then(function(result) { + muffcastUrl = result.muffcast && result.muffcast.url || muffcastUrl; +}) + +var sendServer = function(message) { + return new Promise(function(resolve, reject) { + var xhttp = new XMLHttpRequest(); + xhttp.open("POST", muffcastUrl, true); + xhttp.addEventListener("load", function() { + if (this.readyState == 4) { + if (this.status == 200) { + var response = this.responseText ? JSON.parse(this.responseText) : false; + resolve(response); + } else { + reject({ + status: this.status, + error: this.statusText, + body: this.responseText + }); + } + } + }); + xhttp.setRequestHeader("content-type", "application/json"); + xhttp.send(JSON.stringify(message)); + }) +} + + +var clientUpdate = function() { + browser.tabs.query({ + currentWindow: true, + active: true + }).then(function(tabs) { + var tab = tabs[0]; + browser.tabs.sendMessage( + tab.id, { + command: "update" + } + ); + }); +} + +var injectCss = function(tabId) { + browser.tabs.insertCSS(tabId, { + code: "@font-face { font-family: 'FontAwesome';" + + "src: url('" + browser.extension.getURL("fonts/fontawesome.eot") + "?v=4.7.0');" + + "src: url('" + browser.extension.getURL("fonts/fontawesome.eot") + "?#iefix&v=4.7.0') format('embedded-opentype')," + + "url('" + browser.extension.getURL("fonts/fontawesome.woff2") + "?v=4.7.0') format('woff2')," + + "url('" + browser.extension.getURL("fonts/fontawesome.woff") + "?v=4.7.0') format('woff')," + + "url('" + browser.extension.getURL("fonts/fontawesome.ttf") + "?v=4.7.0') format('truetype')," + + "url('" + browser.extension.getURL("fonts/fontawesome.svg") + "?v=4.7.0#fontawesomeregular') format('svg');" + + "font - weight: normal;" + + "font - style: normal;}" + }); + + browser.tabs.insertCSS(tabId, { + file: "css/font-awesome.css" + }); + + browser.tabs.insertCSS(tabId, { + file: "css/overlay.css" + }); +} + +browser.tabs.onActivated.addListener(function(tab) { + injectCss(tab.id); + clientUpdate(); +}); + +browser.tabs.onUpdated.addListener(function(tabId, changeInfo) { + injectCss(tabId); + if (changeInfo.status === "complete") { + clientUpdate(); + } +}); + +browser.runtime.onMessage.addListener(function(message) { + console.log("send", message); + sendServer(message).then(function(response) { + console.log("response", response); + if (message.command == "load") { + clientUpdate(); + } + }) +}) diff --git a/css/font-awesome.css b/css/font-awesome.css new file mode 100644 index 0000000..2f7b254 --- /dev/null +++ b/css/font-awesome.css @@ -0,0 +1,2928 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +/* FONT PATH + * -------------------------- */ +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* makes the font 33% larger relative to the icon container */ + +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} + +.fa-2x { + font-size: 2em; +} + +.fa-3x { + font-size: 3em; +} + +.fa-4x { + font-size: 4em; +} + +.fa-5x { + font-size: 5em; +} + +.fa-fw { + width: 1.28571429em; + text-align: center; +} + +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} + +.fa-ul>li { + position: relative; +} + +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} + +.fa-li.fa-lg { + left: -1.85714286em; +} + +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} + +.fa-pull-left { + float: left; +} + +.fa-pull-right { + float: right; +} + +.fa.fa-pull-left { + margin-right: .3em; +} + +.fa.fa-pull-right { + margin-left: .3em; +} + +/* Deprecated as of 4.4.0 */ + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +.fa.pull-left { + margin-right: .3em; +} + +.fa.pull-right { + margin-left: .3em; +} + +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} + +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} + +:root .fa-rotate-90, :root .fa-rotate-180, :root .fa-rotate-270, :root .fa-flip-horizontal, :root .fa-flip-vertical { + filter: none; +} + +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} + +.fa-stack-1x, .fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} + +.fa-stack-1x { + line-height: inherit; +} + +.fa-stack-2x { + font-size: 2em; +} + +.fa-inverse { + color: #ffffff; +} + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.fa-glass:before { + content: "\f000"; +} + +.fa-music:before { + content: "\f001"; +} + +.fa-search:before { + content: "\f002"; +} + +.fa-envelope-o:before { + content: "\f003"; +} + +.fa-heart:before { + content: "\f004"; +} + +.fa-star:before { + content: "\f005"; +} + +.fa-star-o:before { + content: "\f006"; +} + +.fa-user:before { + content: "\f007"; +} + +.fa-film:before { + content: "\f008"; +} + +.fa-th-large:before { + content: "\f009"; +} + +.fa-th:before { + content: "\f00a"; +} + +.fa-th-list:before { + content: "\f00b"; +} + +.fa-check:before { + content: "\f00c"; +} + +.fa-remove:before, .fa-close:before, .fa-times:before { + content: "\f00d"; +} + +.fa-search-plus:before { + content: "\f00e"; +} + +.fa-search-minus:before { + content: "\f010"; +} + +.fa-power-off:before { + content: "\f011"; +} + +.fa-signal:before { + content: "\f012"; +} + +.fa-gear:before, .fa-cog:before { + content: "\f013"; +} + +.fa-trash-o:before { + content: "\f014"; +} + +.fa-home:before { + content: "\f015"; +} + +.fa-file-o:before { + content: "\f016"; +} + +.fa-clock-o:before { + content: "\f017"; +} + +.fa-road:before { + content: "\f018"; +} + +.fa-download:before { + content: "\f019"; +} + +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} + +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} + +.fa-inbox:before { + content: "\f01c"; +} + +.fa-play-circle-o:before { + content: "\f01d"; +} + +.fa-rotate-right:before, .fa-repeat:before { + content: "\f01e"; +} + +.fa-refresh:before { + content: "\f021"; +} + +.fa-list-alt:before { + content: "\f022"; +} + +.fa-lock:before { + content: "\f023"; +} + +.fa-flag:before { + content: "\f024"; +} + +.fa-headphones:before { + content: "\f025"; +} + +.fa-volume-off:before { + content: "\f026"; +} + +.fa-volume-down:before { + content: "\f027"; +} + +.fa-volume-up:before { + content: "\f028"; +} + +.fa-qrcode:before { + content: "\f029"; +} + +.fa-barcode:before { + content: "\f02a"; +} + +.fa-tag:before { + content: "\f02b"; +} + +.fa-tags:before { + content: "\f02c"; +} + +.fa-book:before { + content: "\f02d"; +} + +.fa-bookmark:before { + content: "\f02e"; +} + +.fa-print:before { + content: "\f02f"; +} + +.fa-camera:before { + content: "\f030"; +} + +.fa-font:before { + content: "\f031"; +} + +.fa-bold:before { + content: "\f032"; +} + +.fa-italic:before { + content: "\f033"; +} + +.fa-text-height:before { + content: "\f034"; +} + +.fa-text-width:before { + content: "\f035"; +} + +.fa-align-left:before { + content: "\f036"; +} + +.fa-align-center:before { + content: "\f037"; +} + +.fa-align-right:before { + content: "\f038"; +} + +.fa-align-justify:before { + content: "\f039"; +} + +.fa-list:before { + content: "\f03a"; +} + +.fa-dedent:before, .fa-outdent:before { + content: "\f03b"; +} + +.fa-indent:before { + content: "\f03c"; +} + +.fa-video-camera:before { + content: "\f03d"; +} + +.fa-photo:before, .fa-image:before, .fa-picture-o:before { + content: "\f03e"; +} + +.fa-pencil:before { + content: "\f040"; +} + +.fa-map-marker:before { + content: "\f041"; +} + +.fa-adjust:before { + content: "\f042"; +} + +.fa-tint:before { + content: "\f043"; +} + +.fa-edit:before, .fa-pencil-square-o:before { + content: "\f044"; +} + +.fa-share-square-o:before { + content: "\f045"; +} + +.fa-check-square-o:before { + content: "\f046"; +} + +.fa-arrows:before { + content: "\f047"; +} + +.fa-step-backward:before { + content: "\f048"; +} + +.fa-fast-backward:before { + content: "\f049"; +} + +.fa-backward:before { + content: "\f04a"; +} + +.fa-play:before { + content: "\f04b"; +} + +.fa-pause:before { + content: "\f04c"; +} + +.fa-stop:before { + content: "\f04d"; +} + +.fa-forward:before { + content: "\f04e"; +} + +.fa-fast-forward:before { + content: "\f050"; +} + +.fa-step-forward:before { + content: "\f051"; +} + +.fa-eject:before { + content: "\f052"; +} + +.fa-chevron-left:before { + content: "\f053"; +} + +.fa-chevron-right:before { + content: "\f054"; +} + +.fa-plus-circle:before { + content: "\f055"; +} + +.fa-minus-circle:before { + content: "\f056"; +} + +.fa-times-circle:before { + content: "\f057"; +} + +.fa-check-circle:before { + content: "\f058"; +} + +.fa-question-circle:before { + content: "\f059"; +} + +.fa-info-circle:before { + content: "\f05a"; +} + +.fa-crosshairs:before { + content: "\f05b"; +} + +.fa-times-circle-o:before { + content: "\f05c"; +} + +.fa-check-circle-o:before { + content: "\f05d"; +} + +.fa-ban:before { + content: "\f05e"; +} + +.fa-arrow-left:before { + content: "\f060"; +} + +.fa-arrow-right:before { + content: "\f061"; +} + +.fa-arrow-up:before { + content: "\f062"; +} + +.fa-arrow-down:before { + content: "\f063"; +} + +.fa-mail-forward:before, .fa-share:before { + content: "\f064"; +} + +.fa-expand:before { + content: "\f065"; +} + +.fa-compress:before { + content: "\f066"; +} + +.fa-plus:before { + content: "\f067"; +} + +.fa-minus:before { + content: "\f068"; +} + +.fa-asterisk:before { + content: "\f069"; +} + +.fa-exclamation-circle:before { + content: "\f06a"; +} + +.fa-gift:before { + content: "\f06b"; +} + +.fa-leaf:before { + content: "\f06c"; +} + +.fa-fire:before { + content: "\f06d"; +} + +.fa-eye:before { + content: "\f06e"; +} + +.fa-eye-slash:before { + content: "\f070"; +} + +.fa-warning:before, .fa-exclamation-triangle:before { + content: "\f071"; +} + +.fa-plane:before { + content: "\f072"; +} + +.fa-calendar:before { + content: "\f073"; +} + +.fa-random:before { + content: "\f074"; +} + +.fa-comment:before { + content: "\f075"; +} + +.fa-magnet:before { + content: "\f076"; +} + +.fa-chevron-up:before { + content: "\f077"; +} + +.fa-chevron-down:before { + content: "\f078"; +} + +.fa-retweet:before { + content: "\f079"; +} + +.fa-shopping-cart:before { + content: "\f07a"; +} + +.fa-folder:before { + content: "\f07b"; +} + +.fa-folder-open:before { + content: "\f07c"; +} + +.fa-arrows-v:before { + content: "\f07d"; +} + +.fa-arrows-h:before { + content: "\f07e"; +} + +.fa-bar-chart-o:before, .fa-bar-chart:before { + content: "\f080"; +} + +.fa-twitter-square:before { + content: "\f081"; +} + +.fa-facebook-square:before { + content: "\f082"; +} + +.fa-camera-retro:before { + content: "\f083"; +} + +.fa-key:before { + content: "\f084"; +} + +.fa-gears:before, .fa-cogs:before { + content: "\f085"; +} + +.fa-comments:before { + content: "\f086"; +} + +.fa-thumbs-o-up:before { + content: "\f087"; +} + +.fa-thumbs-o-down:before { + content: "\f088"; +} + +.fa-star-half:before { + content: "\f089"; +} + +.fa-heart-o:before { + content: "\f08a"; +} + +.fa-sign-out:before { + content: "\f08b"; +} + +.fa-linkedin-square:before { + content: "\f08c"; +} + +.fa-thumb-tack:before { + content: "\f08d"; +} + +.fa-external-link:before { + content: "\f08e"; +} + +.fa-sign-in:before { + content: "\f090"; +} + +.fa-trophy:before { + content: "\f091"; +} + +.fa-github-square:before { + content: "\f092"; +} + +.fa-upload:before { + content: "\f093"; +} + +.fa-lemon-o:before { + content: "\f094"; +} + +.fa-phone:before { + content: "\f095"; +} + +.fa-square-o:before { + content: "\f096"; +} + +.fa-bookmark-o:before { + content: "\f097"; +} + +.fa-phone-square:before { + content: "\f098"; +} + +.fa-twitter:before { + content: "\f099"; +} + +.fa-facebook-f:before, .fa-facebook:before { + content: "\f09a"; +} + +.fa-github:before { + content: "\f09b"; +} + +.fa-unlock:before { + content: "\f09c"; +} + +.fa-credit-card:before { + content: "\f09d"; +} + +.fa-feed:before, .fa-rss:before { + content: "\f09e"; +} + +.fa-hdd-o:before { + content: "\f0a0"; +} + +.fa-bullhorn:before { + content: "\f0a1"; +} + +.fa-bell:before { + content: "\f0f3"; +} + +.fa-certificate:before { + content: "\f0a3"; +} + +.fa-hand-o-right:before { + content: "\f0a4"; +} + +.fa-hand-o-left:before { + content: "\f0a5"; +} + +.fa-hand-o-up:before { + content: "\f0a6"; +} + +.fa-hand-o-down:before { + content: "\f0a7"; +} + +.fa-arrow-circle-left:before { + content: "\f0a8"; +} + +.fa-arrow-circle-right:before { + content: "\f0a9"; +} + +.fa-arrow-circle-up:before { + content: "\f0aa"; +} + +.fa-arrow-circle-down:before { + content: "\f0ab"; +} + +.fa-globe:before { + content: "\f0ac"; +} + +.fa-wrench:before { + content: "\f0ad"; +} + +.fa-tasks:before { + content: "\f0ae"; +} + +.fa-filter:before { + content: "\f0b0"; +} + +.fa-briefcase:before { + content: "\f0b1"; +} + +.fa-arrows-alt:before { + content: "\f0b2"; +} + +.fa-group:before, .fa-users:before { + content: "\f0c0"; +} + +.fa-chain:before, .fa-link:before { + content: "\f0c1"; +} + +.fa-cloud:before { + content: "\f0c2"; +} + +.fa-flask:before { + content: "\f0c3"; +} + +.fa-cut:before, .fa-scissors:before { + content: "\f0c4"; +} + +.fa-copy:before, .fa-files-o:before { + content: "\f0c5"; +} + +.fa-paperclip:before { + content: "\f0c6"; +} + +.fa-save:before, .fa-floppy-o:before { + content: "\f0c7"; +} + +.fa-square:before { + content: "\f0c8"; +} + +.fa-navicon:before, .fa-reorder:before, .fa-bars:before { + content: "\f0c9"; +} + +.fa-list-ul:before { + content: "\f0ca"; +} + +.fa-list-ol:before { + content: "\f0cb"; +} + +.fa-strikethrough:before { + content: "\f0cc"; +} + +.fa-underline:before { + content: "\f0cd"; +} + +.fa-table:before { + content: "\f0ce"; +} + +.fa-magic:before { + content: "\f0d0"; +} + +.fa-truck:before { + content: "\f0d1"; +} + +.fa-pinterest:before { + content: "\f0d2"; +} + +.fa-pinterest-square:before { + content: "\f0d3"; +} + +.fa-google-plus-square:before { + content: "\f0d4"; +} + +.fa-google-plus:before { + content: "\f0d5"; +} + +.fa-money:before { + content: "\f0d6"; +} + +.fa-caret-down:before { + content: "\f0d7"; +} + +.fa-caret-up:before { + content: "\f0d8"; +} + +.fa-caret-left:before { + content: "\f0d9"; +} + +.fa-caret-right:before { + content: "\f0da"; +} + +.fa-columns:before { + content: "\f0db"; +} + +.fa-unsorted:before, .fa-sort:before { + content: "\f0dc"; +} + +.fa-sort-down:before, .fa-sort-desc:before { + content: "\f0dd"; +} + +.fa-sort-up:before, .fa-sort-asc:before { + content: "\f0de"; +} + +.fa-envelope:before { + content: "\f0e0"; +} + +.fa-linkedin:before { + content: "\f0e1"; +} + +.fa-rotate-left:before, .fa-undo:before { + content: "\f0e2"; +} + +.fa-legal:before, .fa-gavel:before { + content: "\f0e3"; +} + +.fa-dashboard:before, .fa-tachometer:before { + content: "\f0e4"; +} + +.fa-comment-o:before { + content: "\f0e5"; +} + +.fa-comments-o:before { + content: "\f0e6"; +} + +.fa-flash:before, .fa-bolt:before { + content: "\f0e7"; +} + +.fa-sitemap:before { + content: "\f0e8"; +} + +.fa-umbrella:before { + content: "\f0e9"; +} + +.fa-paste:before, .fa-clipboard:before { + content: "\f0ea"; +} + +.fa-lightbulb-o:before { + content: "\f0eb"; +} + +.fa-exchange:before { + content: "\f0ec"; +} + +.fa-cloud-download:before { + content: "\f0ed"; +} + +.fa-cloud-upload:before { + content: "\f0ee"; +} + +.fa-user-md:before { + content: "\f0f0"; +} + +.fa-stethoscope:before { + content: "\f0f1"; +} + +.fa-suitcase:before { + content: "\f0f2"; +} + +.fa-bell-o:before { + content: "\f0a2"; +} + +.fa-coffee:before { + content: "\f0f4"; +} + +.fa-cutlery:before { + content: "\f0f5"; +} + +.fa-file-text-o:before { + content: "\f0f6"; +} + +.fa-building-o:before { + content: "\f0f7"; +} + +.fa-hospital-o:before { + content: "\f0f8"; +} + +.fa-ambulance:before { + content: "\f0f9"; +} + +.fa-medkit:before { + content: "\f0fa"; +} + +.fa-fighter-jet:before { + content: "\f0fb"; +} + +.fa-beer:before { + content: "\f0fc"; +} + +.fa-h-square:before { + content: "\f0fd"; +} + +.fa-plus-square:before { + content: "\f0fe"; +} + +.fa-angle-double-left:before { + content: "\f100"; +} + +.fa-angle-double-right:before { + content: "\f101"; +} + +.fa-angle-double-up:before { + content: "\f102"; +} + +.fa-angle-double-down:before { + content: "\f103"; +} + +.fa-angle-left:before { + content: "\f104"; +} + +.fa-angle-right:before { + content: "\f105"; +} + +.fa-angle-up:before { + content: "\f106"; +} + +.fa-angle-down:before { + content: "\f107"; +} + +.fa-desktop:before { + content: "\f108"; +} + +.fa-laptop:before { + content: "\f109"; +} + +.fa-tablet:before { + content: "\f10a"; +} + +.fa-mobile-phone:before, .fa-mobile:before { + content: "\f10b"; +} + +.fa-circle-o:before { + content: "\f10c"; +} + +.fa-quote-left:before { + content: "\f10d"; +} + +.fa-quote-right:before { + content: "\f10e"; +} + +.fa-spinner:before { + content: "\f110"; +} + +.fa-circle:before { + content: "\f111"; +} + +.fa-mail-reply:before, .fa-reply:before { + content: "\f112"; +} + +.fa-github-alt:before { + content: "\f113"; +} + +.fa-folder-o:before { + content: "\f114"; +} + +.fa-folder-open-o:before { + content: "\f115"; +} + +.fa-smile-o:before { + content: "\f118"; +} + +.fa-frown-o:before { + content: "\f119"; +} + +.fa-meh-o:before { + content: "\f11a"; +} + +.fa-gamepad:before { + content: "\f11b"; +} + +.fa-keyboard-o:before { + content: "\f11c"; +} + +.fa-flag-o:before { + content: "\f11d"; +} + +.fa-flag-checkered:before { + content: "\f11e"; +} + +.fa-terminal:before { + content: "\f120"; +} + +.fa-code:before { + content: "\f121"; +} + +.fa-mail-reply-all:before, .fa-reply-all:before { + content: "\f122"; +} + +.fa-star-half-empty:before, .fa-star-half-full:before, .fa-star-half-o:before { + content: "\f123"; +} + +.fa-location-arrow:before { + content: "\f124"; +} + +.fa-crop:before { + content: "\f125"; +} + +.fa-code-fork:before { + content: "\f126"; +} + +.fa-unlink:before, .fa-chain-broken:before { + content: "\f127"; +} + +.fa-question:before { + content: "\f128"; +} + +.fa-info:before { + content: "\f129"; +} + +.fa-exclamation:before { + content: "\f12a"; +} + +.fa-superscript:before { + content: "\f12b"; +} + +.fa-subscript:before { + content: "\f12c"; +} + +.fa-eraser:before { + content: "\f12d"; +} + +.fa-puzzle-piece:before { + content: "\f12e"; +} + +.fa-microphone:before { + content: "\f130"; +} + +.fa-microphone-slash:before { + content: "\f131"; +} + +.fa-shield:before { + content: "\f132"; +} + +.fa-calendar-o:before { + content: "\f133"; +} + +.fa-fire-extinguisher:before { + content: "\f134"; +} + +.fa-rocket:before { + content: "\f135"; +} + +.fa-maxcdn:before { + content: "\f136"; +} + +.fa-chevron-circle-left:before { + content: "\f137"; +} + +.fa-chevron-circle-right:before { + content: "\f138"; +} + +.fa-chevron-circle-up:before { + content: "\f139"; +} + +.fa-chevron-circle-down:before { + content: "\f13a"; +} + +.fa-html5:before { + content: "\f13b"; +} + +.fa-css3:before { + content: "\f13c"; +} + +.fa-anchor:before { + content: "\f13d"; +} + +.fa-unlock-alt:before { + content: "\f13e"; +} + +.fa-bullseye:before { + content: "\f140"; +} + +.fa-ellipsis-h:before { + content: "\f141"; +} + +.fa-ellipsis-v:before { + content: "\f142"; +} + +.fa-rss-square:before { + content: "\f143"; +} + +.fa-play-circle:before { + content: "\f144"; +} + +.fa-ticket:before { + content: "\f145"; +} + +.fa-minus-square:before { + content: "\f146"; +} + +.fa-minus-square-o:before { + content: "\f147"; +} + +.fa-level-up:before { + content: "\f148"; +} + +.fa-level-down:before { + content: "\f149"; +} + +.fa-check-square:before { + content: "\f14a"; +} + +.fa-pencil-square:before { + content: "\f14b"; +} + +.fa-external-link-square:before { + content: "\f14c"; +} + +.fa-share-square:before { + content: "\f14d"; +} + +.fa-compass:before { + content: "\f14e"; +} + +.fa-toggle-down:before, .fa-caret-square-o-down:before { + content: "\f150"; +} + +.fa-toggle-up:before, .fa-caret-square-o-up:before { + content: "\f151"; +} + +.fa-toggle-right:before, .fa-caret-square-o-right:before { + content: "\f152"; +} + +.fa-euro:before, .fa-eur:before { + content: "\f153"; +} + +.fa-gbp:before { + content: "\f154"; +} + +.fa-dollar:before, .fa-usd:before { + content: "\f155"; +} + +.fa-rupee:before, .fa-inr:before { + content: "\f156"; +} + +.fa-cny:before, .fa-rmb:before, .fa-yen:before, .fa-jpy:before { + content: "\f157"; +} + +.fa-ruble:before, .fa-rouble:before, .fa-rub:before { + content: "\f158"; +} + +.fa-won:before, .fa-krw:before { + content: "\f159"; +} + +.fa-bitcoin:before, .fa-btc:before { + content: "\f15a"; +} + +.fa-file:before { + content: "\f15b"; +} + +.fa-file-text:before { + content: "\f15c"; +} + +.fa-sort-alpha-asc:before { + content: "\f15d"; +} + +.fa-sort-alpha-desc:before { + content: "\f15e"; +} + +.fa-sort-amount-asc:before { + content: "\f160"; +} + +.fa-sort-amount-desc:before { + content: "\f161"; +} + +.fa-sort-numeric-asc:before { + content: "\f162"; +} + +.fa-sort-numeric-desc:before { + content: "\f163"; +} + +.fa-thumbs-up:before { + content: "\f164"; +} + +.fa-thumbs-down:before { + content: "\f165"; +} + +.fa-youtube-square:before { + content: "\f166"; +} + +.fa-youtube:before { + content: "\f167"; +} + +.fa-xing:before { + content: "\f168"; +} + +.fa-xing-square:before { + content: "\f169"; +} + +.fa-youtube-play:before { + content: "\f16a"; +} + +.fa-dropbox:before { + content: "\f16b"; +} + +.fa-stack-overflow:before { + content: "\f16c"; +} + +.fa-instagram:before { + content: "\f16d"; +} + +.fa-flickr:before { + content: "\f16e"; +} + +.fa-adn:before { + content: "\f170"; +} + +.fa-bitbucket:before { + content: "\f171"; +} + +.fa-bitbucket-square:before { + content: "\f172"; +} + +.fa-tumblr:before { + content: "\f173"; +} + +.fa-tumblr-square:before { + content: "\f174"; +} + +.fa-long-arrow-down:before { + content: "\f175"; +} + +.fa-long-arrow-up:before { + content: "\f176"; +} + +.fa-long-arrow-left:before { + content: "\f177"; +} + +.fa-long-arrow-right:before { + content: "\f178"; +} + +.fa-apple:before { + content: "\f179"; +} + +.fa-windows:before { + content: "\f17a"; +} + +.fa-android:before { + content: "\f17b"; +} + +.fa-linux:before { + content: "\f17c"; +} + +.fa-dribbble:before { + content: "\f17d"; +} + +.fa-skype:before { + content: "\f17e"; +} + +.fa-foursquare:before { + content: "\f180"; +} + +.fa-trello:before { + content: "\f181"; +} + +.fa-female:before { + content: "\f182"; +} + +.fa-male:before { + content: "\f183"; +} + +.fa-gittip:before, .fa-gratipay:before { + content: "\f184"; +} + +.fa-sun-o:before { + content: "\f185"; +} + +.fa-moon-o:before { + content: "\f186"; +} + +.fa-archive:before { + content: "\f187"; +} + +.fa-bug:before { + content: "\f188"; +} + +.fa-vk:before { + content: "\f189"; +} + +.fa-weibo:before { + content: "\f18a"; +} + +.fa-renren:before { + content: "\f18b"; +} + +.fa-pagelines:before { + content: "\f18c"; +} + +.fa-stack-exchange:before { + content: "\f18d"; +} + +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} + +.fa-arrow-circle-o-left:before { + content: "\f190"; +} + +.fa-toggle-left:before, .fa-caret-square-o-left:before { + content: "\f191"; +} + +.fa-dot-circle-o:before { + content: "\f192"; +} + +.fa-wheelchair:before { + content: "\f193"; +} + +.fa-vimeo-square:before { + content: "\f194"; +} + +.fa-turkish-lira:before, .fa-try:before { + content: "\f195"; +} + +.fa-plus-square-o:before { + content: "\f196"; +} + +.fa-space-shuttle:before { + content: "\f197"; +} + +.fa-slack:before { + content: "\f198"; +} + +.fa-envelope-square:before { + content: "\f199"; +} + +.fa-wordpress:before { + content: "\f19a"; +} + +.fa-openid:before { + content: "\f19b"; +} + +.fa-institution:before, .fa-bank:before, .fa-university:before { + content: "\f19c"; +} + +.fa-mortar-board:before, .fa-graduation-cap:before { + content: "\f19d"; +} + +.fa-yahoo:before { + content: "\f19e"; +} + +.fa-google:before { + content: "\f1a0"; +} + +.fa-reddit:before { + content: "\f1a1"; +} + +.fa-reddit-square:before { + content: "\f1a2"; +} + +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} + +.fa-stumbleupon:before { + content: "\f1a4"; +} + +.fa-delicious:before { + content: "\f1a5"; +} + +.fa-digg:before { + content: "\f1a6"; +} + +.fa-pied-piper-pp:before { + content: "\f1a7"; +} + +.fa-pied-piper-alt:before { + content: "\f1a8"; +} + +.fa-drupal:before { + content: "\f1a9"; +} + +.fa-joomla:before { + content: "\f1aa"; +} + +.fa-language:before { + content: "\f1ab"; +} + +.fa-fax:before { + content: "\f1ac"; +} + +.fa-building:before { + content: "\f1ad"; +} + +.fa-child:before { + content: "\f1ae"; +} + +.fa-paw:before { + content: "\f1b0"; +} + +.fa-spoon:before { + content: "\f1b1"; +} + +.fa-cube:before { + content: "\f1b2"; +} + +.fa-cubes:before { + content: "\f1b3"; +} + +.fa-behance:before { + content: "\f1b4"; +} + +.fa-behance-square:before { + content: "\f1b5"; +} + +.fa-steam:before { + content: "\f1b6"; +} + +.fa-steam-square:before { + content: "\f1b7"; +} + +.fa-recycle:before { + content: "\f1b8"; +} + +.fa-automobile:before, .fa-car:before { + content: "\f1b9"; +} + +.fa-cab:before, .fa-taxi:before { + content: "\f1ba"; +} + +.fa-tree:before { + content: "\f1bb"; +} + +.fa-spotify:before { + content: "\f1bc"; +} + +.fa-deviantart:before { + content: "\f1bd"; +} + +.fa-soundcloud:before { + content: "\f1be"; +} + +.fa-database:before { + content: "\f1c0"; +} + +.fa-file-pdf-o:before { + content: "\f1c1"; +} + +.fa-file-word-o:before { + content: "\f1c2"; +} + +.fa-file-excel-o:before { + content: "\f1c3"; +} + +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} + +.fa-file-photo-o:before, .fa-file-picture-o:before, .fa-file-image-o:before { + content: "\f1c5"; +} + +.fa-file-zip-o:before, .fa-file-archive-o:before { + content: "\f1c6"; +} + +.fa-file-sound-o:before, .fa-file-audio-o:before { + content: "\f1c7"; +} + +.fa-file-movie-o:before, .fa-file-video-o:before { + content: "\f1c8"; +} + +.fa-file-code-o:before { + content: "\f1c9"; +} + +.fa-vine:before { + content: "\f1ca"; +} + +.fa-codepen:before { + content: "\f1cb"; +} + +.fa-jsfiddle:before { + content: "\f1cc"; +} + +.fa-life-bouy:before, .fa-life-buoy:before, .fa-life-saver:before, .fa-support:before, .fa-life-ring:before { + content: "\f1cd"; +} + +.fa-circle-o-notch:before { + content: "\f1ce"; +} + +.fa-ra:before, .fa-resistance:before, .fa-rebel:before { + content: "\f1d0"; +} + +.fa-ge:before, .fa-empire:before { + content: "\f1d1"; +} + +.fa-git-square:before { + content: "\f1d2"; +} + +.fa-git:before { + content: "\f1d3"; +} + +.fa-y-combinator-square:before, .fa-yc-square:before, .fa-hacker-news:before { + content: "\f1d4"; +} + +.fa-tencent-weibo:before { + content: "\f1d5"; +} + +.fa-qq:before { + content: "\f1d6"; +} + +.fa-wechat:before, .fa-weixin:before { + content: "\f1d7"; +} + +.fa-send:before, .fa-paper-plane:before { + content: "\f1d8"; +} + +.fa-send-o:before, .fa-paper-plane-o:before { + content: "\f1d9"; +} + +.fa-history:before { + content: "\f1da"; +} + +.fa-circle-thin:before { + content: "\f1db"; +} + +.fa-header:before { + content: "\f1dc"; +} + +.fa-paragraph:before { + content: "\f1dd"; +} + +.fa-sliders:before { + content: "\f1de"; +} + +.fa-share-alt:before { + content: "\f1e0"; +} + +.fa-share-alt-square:before { + content: "\f1e1"; +} + +.fa-bomb:before { + content: "\f1e2"; +} + +.fa-soccer-ball-o:before, .fa-futbol-o:before { + content: "\f1e3"; +} + +.fa-tty:before { + content: "\f1e4"; +} + +.fa-binoculars:before { + content: "\f1e5"; +} + +.fa-plug:before { + content: "\f1e6"; +} + +.fa-slideshare:before { + content: "\f1e7"; +} + +.fa-twitch:before { + content: "\f1e8"; +} + +.fa-yelp:before { + content: "\f1e9"; +} + +.fa-newspaper-o:before { + content: "\f1ea"; +} + +.fa-wifi:before { + content: "\f1eb"; +} + +.fa-calculator:before { + content: "\f1ec"; +} + +.fa-paypal:before { + content: "\f1ed"; +} + +.fa-google-wallet:before { + content: "\f1ee"; +} + +.fa-cc-visa:before { + content: "\f1f0"; +} + +.fa-cc-mastercard:before { + content: "\f1f1"; +} + +.fa-cc-discover:before { + content: "\f1f2"; +} + +.fa-cc-amex:before { + content: "\f1f3"; +} + +.fa-cc-paypal:before { + content: "\f1f4"; +} + +.fa-cc-stripe:before { + content: "\f1f5"; +} + +.fa-bell-slash:before { + content: "\f1f6"; +} + +.fa-bell-slash-o:before { + content: "\f1f7"; +} + +.fa-trash:before { + content: "\f1f8"; +} + +.fa-copyright:before { + content: "\f1f9"; +} + +.fa-at:before { + content: "\f1fa"; +} + +.fa-eyedropper:before { + content: "\f1fb"; +} + +.fa-paint-brush:before { + content: "\f1fc"; +} + +.fa-birthday-cake:before { + content: "\f1fd"; +} + +.fa-area-chart:before { + content: "\f1fe"; +} + +.fa-pie-chart:before { + content: "\f200"; +} + +.fa-line-chart:before { + content: "\f201"; +} + +.fa-lastfm:before { + content: "\f202"; +} + +.fa-lastfm-square:before { + content: "\f203"; +} + +.fa-toggle-off:before { + content: "\f204"; +} + +.fa-toggle-on:before { + content: "\f205"; +} + +.fa-bicycle:before { + content: "\f206"; +} + +.fa-bus:before { + content: "\f207"; +} + +.fa-ioxhost:before { + content: "\f208"; +} + +.fa-angellist:before { + content: "\f209"; +} + +.fa-cc:before { + content: "\f20a"; +} + +.fa-shekel:before, .fa-sheqel:before, .fa-ils:before { + content: "\f20b"; +} + +.fa-meanpath:before { + content: "\f20c"; +} + +.fa-buysellads:before { + content: "\f20d"; +} + +.fa-connectdevelop:before { + content: "\f20e"; +} + +.fa-dashcube:before { + content: "\f210"; +} + +.fa-forumbee:before { + content: "\f211"; +} + +.fa-leanpub:before { + content: "\f212"; +} + +.fa-sellsy:before { + content: "\f213"; +} + +.fa-shirtsinbulk:before { + content: "\f214"; +} + +.fa-simplybuilt:before { + content: "\f215"; +} + +.fa-skyatlas:before { + content: "\f216"; +} + +.fa-cart-plus:before { + content: "\f217"; +} + +.fa-cart-arrow-down:before { + content: "\f218"; +} + +.fa-diamond:before { + content: "\f219"; +} + +.fa-ship:before { + content: "\f21a"; +} + +.fa-user-secret:before { + content: "\f21b"; +} + +.fa-motorcycle:before { + content: "\f21c"; +} + +.fa-street-view:before { + content: "\f21d"; +} + +.fa-heartbeat:before { + content: "\f21e"; +} + +.fa-venus:before { + content: "\f221"; +} + +.fa-mars:before { + content: "\f222"; +} + +.fa-mercury:before { + content: "\f223"; +} + +.fa-intersex:before, .fa-transgender:before { + content: "\f224"; +} + +.fa-transgender-alt:before { + content: "\f225"; +} + +.fa-venus-double:before { + content: "\f226"; +} + +.fa-mars-double:before { + content: "\f227"; +} + +.fa-venus-mars:before { + content: "\f228"; +} + +.fa-mars-stroke:before { + content: "\f229"; +} + +.fa-mars-stroke-v:before { + content: "\f22a"; +} + +.fa-mars-stroke-h:before { + content: "\f22b"; +} + +.fa-neuter:before { + content: "\f22c"; +} + +.fa-genderless:before { + content: "\f22d"; +} + +.fa-facebook-official:before { + content: "\f230"; +} + +.fa-pinterest-p:before { + content: "\f231"; +} + +.fa-whatsapp:before { + content: "\f232"; +} + +.fa-server:before { + content: "\f233"; +} + +.fa-user-plus:before { + content: "\f234"; +} + +.fa-user-times:before { + content: "\f235"; +} + +.fa-hotel:before, .fa-bed:before { + content: "\f236"; +} + +.fa-viacoin:before { + content: "\f237"; +} + +.fa-train:before { + content: "\f238"; +} + +.fa-subway:before { + content: "\f239"; +} + +.fa-medium:before { + content: "\f23a"; +} + +.fa-yc:before, .fa-y-combinator:before { + content: "\f23b"; +} + +.fa-optin-monster:before { + content: "\f23c"; +} + +.fa-opencart:before { + content: "\f23d"; +} + +.fa-expeditedssl:before { + content: "\f23e"; +} + +.fa-battery-4:before, .fa-battery:before, .fa-battery-full:before { + content: "\f240"; +} + +.fa-battery-3:before, .fa-battery-three-quarters:before { + content: "\f241"; +} + +.fa-battery-2:before, .fa-battery-half:before { + content: "\f242"; +} + +.fa-battery-1:before, .fa-battery-quarter:before { + content: "\f243"; +} + +.fa-battery-0:before, .fa-battery-empty:before { + content: "\f244"; +} + +.fa-mouse-pointer:before { + content: "\f245"; +} + +.fa-i-cursor:before { + content: "\f246"; +} + +.fa-object-group:before { + content: "\f247"; +} + +.fa-object-ungroup:before { + content: "\f248"; +} + +.fa-sticky-note:before { + content: "\f249"; +} + +.fa-sticky-note-o:before { + content: "\f24a"; +} + +.fa-cc-jcb:before { + content: "\f24b"; +} + +.fa-cc-diners-club:before { + content: "\f24c"; +} + +.fa-clone:before { + content: "\f24d"; +} + +.fa-balance-scale:before { + content: "\f24e"; +} + +.fa-hourglass-o:before { + content: "\f250"; +} + +.fa-hourglass-1:before, .fa-hourglass-start:before { + content: "\f251"; +} + +.fa-hourglass-2:before, .fa-hourglass-half:before { + content: "\f252"; +} + +.fa-hourglass-3:before, .fa-hourglass-end:before { + content: "\f253"; +} + +.fa-hourglass:before { + content: "\f254"; +} + +.fa-hand-grab-o:before, .fa-hand-rock-o:before { + content: "\f255"; +} + +.fa-hand-stop-o:before, .fa-hand-paper-o:before { + content: "\f256"; +} + +.fa-hand-scissors-o:before { + content: "\f257"; +} + +.fa-hand-lizard-o:before { + content: "\f258"; +} + +.fa-hand-spock-o:before { + content: "\f259"; +} + +.fa-hand-pointer-o:before { + content: "\f25a"; +} + +.fa-hand-peace-o:before { + content: "\f25b"; +} + +.fa-trademark:before { + content: "\f25c"; +} + +.fa-registered:before { + content: "\f25d"; +} + +.fa-creative-commons:before { + content: "\f25e"; +} + +.fa-gg:before { + content: "\f260"; +} + +.fa-gg-circle:before { + content: "\f261"; +} + +.fa-tripadvisor:before { + content: "\f262"; +} + +.fa-odnoklassniki:before { + content: "\f263"; +} + +.fa-odnoklassniki-square:before { + content: "\f264"; +} + +.fa-get-pocket:before { + content: "\f265"; +} + +.fa-wikipedia-w:before { + content: "\f266"; +} + +.fa-safari:before { + content: "\f267"; +} + +.fa-chrome:before { + content: "\f268"; +} + +.fa-firefox:before { + content: "\f269"; +} + +.fa-opera:before { + content: "\f26a"; +} + +.fa-internet-explorer:before { + content: "\f26b"; +} + +.fa-tv:before, .fa-television:before { + content: "\f26c"; +} + +.fa-contao:before { + content: "\f26d"; +} + +.fa-500px:before { + content: "\f26e"; +} + +.fa-amazon:before { + content: "\f270"; +} + +.fa-calendar-plus-o:before { + content: "\f271"; +} + +.fa-calendar-minus-o:before { + content: "\f272"; +} + +.fa-calendar-times-o:before { + content: "\f273"; +} + +.fa-calendar-check-o:before { + content: "\f274"; +} + +.fa-industry:before { + content: "\f275"; +} + +.fa-map-pin:before { + content: "\f276"; +} + +.fa-map-signs:before { + content: "\f277"; +} + +.fa-map-o:before { + content: "\f278"; +} + +.fa-map:before { + content: "\f279"; +} + +.fa-commenting:before { + content: "\f27a"; +} + +.fa-commenting-o:before { + content: "\f27b"; +} + +.fa-houzz:before { + content: "\f27c"; +} + +.fa-vimeo:before { + content: "\f27d"; +} + +.fa-black-tie:before { + content: "\f27e"; +} + +.fa-fonticons:before { + content: "\f280"; +} + +.fa-reddit-alien:before { + content: "\f281"; +} + +.fa-edge:before { + content: "\f282"; +} + +.fa-credit-card-alt:before { + content: "\f283"; +} + +.fa-codiepie:before { + content: "\f284"; +} + +.fa-modx:before { + content: "\f285"; +} + +.fa-fort-awesome:before { + content: "\f286"; +} + +.fa-usb:before { + content: "\f287"; +} + +.fa-product-hunt:before { + content: "\f288"; +} + +.fa-mixcloud:before { + content: "\f289"; +} + +.fa-scribd:before { + content: "\f28a"; +} + +.fa-pause-circle:before { + content: "\f28b"; +} + +.fa-pause-circle-o:before { + content: "\f28c"; +} + +.fa-stop-circle:before { + content: "\f28d"; +} + +.fa-stop-circle-o:before { + content: "\f28e"; +} + +.fa-shopping-bag:before { + content: "\f290"; +} + +.fa-shopping-basket:before { + content: "\f291"; +} + +.fa-hashtag:before { + content: "\f292"; +} + +.fa-bluetooth:before { + content: "\f293"; +} + +.fa-bluetooth-b:before { + content: "\f294"; +} + +.fa-percent:before { + content: "\f295"; +} + +.fa-gitlab:before { + content: "\f296"; +} + +.fa-wpbeginner:before { + content: "\f297"; +} + +.fa-wpforms:before { + content: "\f298"; +} + +.fa-envira:before { + content: "\f299"; +} + +.fa-universal-access:before { + content: "\f29a"; +} + +.fa-wheelchair-alt:before { + content: "\f29b"; +} + +.fa-question-circle-o:before { + content: "\f29c"; +} + +.fa-blind:before { + content: "\f29d"; +} + +.fa-audio-description:before { + content: "\f29e"; +} + +.fa-volume-control-phone:before { + content: "\f2a0"; +} + +.fa-braille:before { + content: "\f2a1"; +} + +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} + +.fa-asl-interpreting:before, .fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} + +.fa-deafness:before, .fa-hard-of-hearing:before, .fa-deaf:before { + content: "\f2a4"; +} + +.fa-glide:before { + content: "\f2a5"; +} + +.fa-glide-g:before { + content: "\f2a6"; +} + +.fa-signing:before, .fa-sign-language:before { + content: "\f2a7"; +} + +.fa-low-vision:before { + content: "\f2a8"; +} + +.fa-viadeo:before { + content: "\f2a9"; +} + +.fa-viadeo-square:before { + content: "\f2aa"; +} + +.fa-snapchat:before { + content: "\f2ab"; +} + +.fa-snapchat-ghost:before { + content: "\f2ac"; +} + +.fa-snapchat-square:before { + content: "\f2ad"; +} + +.fa-pied-piper:before { + content: "\f2ae"; +} + +.fa-first-order:before { + content: "\f2b0"; +} + +.fa-yoast:before { + content: "\f2b1"; +} + +.fa-themeisle:before { + content: "\f2b2"; +} + +.fa-google-plus-circle:before, .fa-google-plus-official:before { + content: "\f2b3"; +} + +.fa-fa:before, .fa-font-awesome:before { + content: "\f2b4"; +} + +.fa-handshake-o:before { + content: "\f2b5"; +} + +.fa-envelope-open:before { + content: "\f2b6"; +} + +.fa-envelope-open-o:before { + content: "\f2b7"; +} + +.fa-linode:before { + content: "\f2b8"; +} + +.fa-address-book:before { + content: "\f2b9"; +} + +.fa-address-book-o:before { + content: "\f2ba"; +} + +.fa-vcard:before, .fa-address-card:before { + content: "\f2bb"; +} + +.fa-vcard-o:before, .fa-address-card-o:before { + content: "\f2bc"; +} + +.fa-user-circle:before { + content: "\f2bd"; +} + +.fa-user-circle-o:before { + content: "\f2be"; +} + +.fa-user-o:before { + content: "\f2c0"; +} + +.fa-id-badge:before { + content: "\f2c1"; +} + +.fa-drivers-license:before, .fa-id-card:before { + content: "\f2c2"; +} + +.fa-drivers-license-o:before, .fa-id-card-o:before { + content: "\f2c3"; +} + +.fa-quora:before { + content: "\f2c4"; +} + +.fa-free-code-camp:before { + content: "\f2c5"; +} + +.fa-telegram:before { + content: "\f2c6"; +} + +.fa-thermometer-4:before, .fa-thermometer:before, .fa-thermometer-full:before { + content: "\f2c7"; +} + +.fa-thermometer-3:before, .fa-thermometer-three-quarters:before { + content: "\f2c8"; +} + +.fa-thermometer-2:before, .fa-thermometer-half:before { + content: "\f2c9"; +} + +.fa-thermometer-1:before, .fa-thermometer-quarter:before { + content: "\f2ca"; +} + +.fa-thermometer-0:before, .fa-thermometer-empty:before { + content: "\f2cb"; +} + +.fa-shower:before { + content: "\f2cc"; +} + +.fa-bathtub:before, .fa-s15:before, .fa-bath:before { + content: "\f2cd"; +} + +.fa-podcast:before { + content: "\f2ce"; +} + +.fa-window-maximize:before { + content: "\f2d0"; +} + +.fa-window-minimize:before { + content: "\f2d1"; +} + +.fa-window-restore:before { + content: "\f2d2"; +} + +.fa-times-rectangle:before, .fa-window-close:before { + content: "\f2d3"; +} + +.fa-times-rectangle-o:before, .fa-window-close-o:before { + content: "\f2d4"; +} + +.fa-bandcamp:before { + content: "\f2d5"; +} + +.fa-grav:before { + content: "\f2d6"; +} + +.fa-etsy:before { + content: "\f2d7"; +} + +.fa-imdb:before { + content: "\f2d8"; +} + +.fa-ravelry:before { + content: "\f2d9"; +} + +.fa-eercast:before { + content: "\f2da"; +} + +.fa-microchip:before { + content: "\f2db"; +} + +.fa-snowflake-o:before { + content: "\f2dc"; +} + +.fa-superpowers:before { + content: "\f2dd"; +} + +.fa-wpexplorer:before { + content: "\f2de"; +} + +.fa-meetup:before { + content: "\f2e0"; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/css/overlay.css b/css/overlay.css new file mode 100644 index 0000000..9d8dbc0 --- /dev/null +++ b/css/overlay.css @@ -0,0 +1,117 @@ +a.muffcast-loader { + position: absolute; + color: #d13d29; + font-size: 20px; + z-index: 8128; + padding-left: 3px; + padding-top: 1px; + cursor: pointer; +} + +a.muffcast-loader:hover { + color: #9e2e1f; +} + +a.muffcast-loader.active { + color: #468ac3; +} + +#muffcast-overlay { + font-family: 'Roboto', 'sans-serif', 'sans'; + box-sizing: border-box; + display: -moz-flex; + display: flex; + width: 100%; + height: 55px; + padding: 10px; + background-color: #efefef; + position: fixed; + left: 0px; + bottom: 0px; + z-index: 2000; +} + +#muffcast-overlay * { + box-sizing: border-box; +} + +#muffcast-overlay a { + padding: 5px; + min-width: 35px; + color: #d13d29; + font-size: 18px; + cursor: pointer; +} + +#muffcast-overlay a:hover { + color: #9e2e1f; +} + +#muffcast-overlay span.time { + padding: 11px 4px; + font-size: 10px; +} + +#muffcast-overlay #muffcast-icon { + color: #468ac3; + font-size: 24px; + padding: 2px 4px; +} + +#muffcast-overlay #muffcast-audio { + position: relative; + width: 35px; + min-width: 35px; + height: 35px; + overflow: hidden; + transition: width 0.3s, min-width 0.3s; + transition-timing-function: ease-out; +} + +#muffcast-overlay #muffcast-audio:hover { + width: 160px; + min-width: 160px; +} + +#muffcast-overlay #muffcast-mute { + float: left; +} + +#muffcast-overlay input[type="range"] { + float: left; + margin: 0; + height: 35px; + cursor: pointer; +} + +#muffcast-overlay input[type="range"]::-moz-range-track { + width: 100%; + height: 3px; + cursor: pointer; + background: #468ac3; + border-radius: 2px; +} + +#muffcast-overlay input[type="range"]::-moz-range-thumb { + box-shadow: 0px 0px 0px #d13d29; + border: 0px solid #d13d29; + height: 14px; + width: 14px; + border-radius: 50%; + background: #d13d29; + cursor: pointer; +} + +#muffcast-overlay input[type="range"]::-moz-range-thumb:hover { + color: #9e2e1f; + height: 16px; + width: 16px; +} + +#muffcast-overlay #muffcast-volume { + width: 120px; +} + +#muffcast-overlay #muffcast-seek { + flex-grow: 1; +} diff --git a/fonts/FontAwesome.otf b/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/fonts/FontAwesome.otf differ diff --git a/fonts/fontawesome.eot b/fonts/fontawesome.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/fonts/fontawesome.eot differ diff --git a/fonts/fontawesome.svg b/fonts/fontawesome.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/fonts/fontawesome.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fonts/fontawesome.ttf b/fonts/fontawesome.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/fonts/fontawesome.ttf differ diff --git a/fonts/fontawesome.woff b/fonts/fontawesome.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/fonts/fontawesome.woff differ diff --git a/fonts/fontawesome.woff2 b/fonts/fontawesome.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/fonts/fontawesome.woff2 differ diff --git a/icons/muffcast-client-32-light.png b/icons/muffcast-client-32-light.png new file mode 100644 index 0000000..c5e924e Binary files /dev/null and b/icons/muffcast-client-32-light.png differ diff --git a/icons/muffcast-client-32.png b/icons/muffcast-client-32.png new file mode 100644 index 0000000..5cd9f24 Binary files /dev/null and b/icons/muffcast-client-32.png differ diff --git a/icons/muffcast-client-48.png b/icons/muffcast-client-48.png new file mode 100644 index 0000000..2e8b0d1 Binary files /dev/null and b/icons/muffcast-client-48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..a9e665d --- /dev/null +++ b/manifest.json @@ -0,0 +1,61 @@ +{ + + "description": "Cast HTML5 video to other Firefox instance. This is the client extension. Setup: define url of the Muffcast Server Extension, running on any device in your network on port 8128. After Setup, go to any website with HTML5 Video elements and cast it on server.", + "manifest_version": 2, + "name": "Muffcast", + "version": "0.1.3", + "homepage_url": "https://www.champonthis.de/projects/muffcast", + "icons": { + "48": "icons/muffcast-client-48.png" + }, + + "applications": { + "gecko": { + "id": "muffcast-client@champonthis.de" + } + }, + + "permissions": [ + "activeTab", + "http://*/", + "https://*/", + "nativeMessaging", + "storage" + ], + + "background": { + "scripts": ["background/client.js"] + }, + + "browser_action": { + "default_icon": "icons/muffcast-client-32.png", + "theme_icons": [{ + "light": "icons/muffcast-client-32-light.png", + "dark": "icons/muffcast-client-32.png", + "size": 32 + }], + "default_title": "Muffcast", + "default_popup": "popup/popup.html" + }, + + "content_scripts": [{ + "matches": [""], + "js": ["muffcast.js"] + }], + + "options_ui": { + "page": "options/options.html", + "browser_style": true + }, + + "web_accessible_resources": [ + "icons/muffcast-client-32.png", + "fonts/fontawesome.eot", + "fonts/fontawesome.otf", + "fonts/fontawesome.svg", + "fonts/fontawesome.ttf", + "fonts/fontawesome.woff", + "fonts/fontawesome.woff2" + ] + +} diff --git a/muffcast.js b/muffcast.js new file mode 100644 index 0000000..4f520dd --- /dev/null +++ b/muffcast.js @@ -0,0 +1,358 @@ +console.log("muffcast client v0.1"); + +var currentStatus; +var syncIntervals = []; +var syncIntervalTime = 30000; +var seekIntervals = []; +var muffcastUrl = "http://localhost:8128"; + +browser.storage.local.get("muffcast").then(function(result) { + muffcastUrl = result.muffcast && result.muffcast.url || muffcastUrl; + syncIntervalTime = result.muffcast && result.muffcast.syncInterval && parseInt(result.muffcast.syncInterval) || syncIntervalTime; +}) + +var getStatus = function() { + return new Promise(function(resolve, reject) { + var xhttp = new XMLHttpRequest(); + xhttp.open("GET", muffcastUrl, true); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + var response = this.responseText ? JSON.parse(this.responseText) : false; + resolve(response); + } else { + reject({ + status: this.status, + error: this.statusText, + body: this.responseText, + }); + } + } + } + xhttp.setRequestHeader("Content-type", "application/json"); + xhttp.send(); + }) +} + +var getPlayer = function(type, index, sleep) { + return new Promise(function(resolve, reject) { + setTimeout(function() { + var player = document.getElementsByTagName(type)[index]; + if (player) { + resolve(player); + } else if (sleep < 3000) { + return getPlayer(type, index, sleep + 500); + } else { + reject(player); + } + }, sleep); + }) +} + +var getTimeString = function(seconds) { + var hours = parseInt(seconds / 3600); + var minutes = parseInt((seconds % 3600) / 60); + var seconds = parseInt(seconds % 60); + + return (hours > 0 ? hours + ":" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; +} + +var addCastLinks = function(type) { + // add cast links + var elements = document.getElementsByTagName(type); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + var position = element.getBoundingClientRect(); + var castLink = document.createElement("a"); + castLink.id = "muffcast-cast-link_" + i; + castLink.index = i; + castLink.classList.add("muffcast-loader"); + castLink.style["top"] = position.top + "px"; + castLink.style["left"] = position.left + "px"; + castLink.innerHTML = ''; + document.body.appendChild(castLink); + castLink.addEventListener("click", function(event) { + var index = event.target.parentNode.index; + var player = document.getElementsByTagName(type)[index]; + player.pause(); + browser.runtime.sendMessage({ + "command": "load", + "url": encodeURIComponent(window.location.href), + "type": type, + "index": index, + "seek": player.currentTime, + "volume": player.volume, + "muted": player.muted + }); + }) + } +} + + +var setStatus = function() { + for (let seekInterval of seekIntervals) { + clearInterval(seekInterval); + } + for (let syncInterval of syncIntervals) { + clearInterval(syncInterval); + } + + var overlay = document.getElementById("muffcast-overlay"); + + if (overlay) { + overlay.parentNode.removeChild(overlay); + } + + + // remove all cast links + for (let castLink of document.getElementsByClassName("muffcast-loader")) { + document.body.removeChild(castLink); + } + + addCastLinks("video"); + addCastLinks("audio"); + + getStatus().then(function(status) { + currentStatus = status; + if (currentStatus.url && currentStatus.url == encodeURIComponent(window.location.href)) { + getPlayer(currentStatus.type, currentStatus.index, 0).then(function(player) { + player.muted = currentStatus.muted; + player.currentTime = currentStatus.currentTime; + + player.addEventListener("canplaythrough", function() { + if (currentStatus.playing && currentStatus.url == encodeURIComponent(window.location.href)) { + player.pause(); + } + }) + + overlay = document.createElement("div"); + overlay.id = "muffcast-overlay"; + document.body.appendChild(overlay); + + var play = document.createElement("a"); + play.id = "muffcast-play"; + play.innerHTML = currentStatus.playing ? '' : '';; + + play.addEventListener("click", function(event) { + if (currentStatus.playing) { + browser.runtime.sendMessage({ + "command": "pause", + "seek": player.currentTime + }); + } else { + browser.runtime.sendMessage({ + "command": "play", + "seek": player.currentTime + }); + player.pause(); + } + currentStatus.playing = !currentStatus.playing; + play.innerHTML = currentStatus.playing ? '' : ''; + }) + + var stop = document.createElement("a"); + stop.id = "muffcast-stop"; + stop.innerHTML = ''; + stop.addEventListener("click", function(event) { + browser.runtime.sendMessage({ + "command": "stop" + }); + setStatus(); + }) + + var duration = document.createElement("span"); + duration.id = "muffcast-duration"; + duration.classList.add("time"); + duration.textContent = getTimeString(player.duration); + + var currentTime = document.createElement("span"); + currentTime.id = "muffcast-currenttime"; + currentTime.classList.add("time"); + currentTime.textContent = getTimeString(player.currentTime); + + var seek = document.createElement("input"); + seek.id = "muffcast-seek"; + seek.setAttribute("type", "range"); + seek.setAttribute("min", 0); + seek.setAttribute("max", currentStatus.duration); + seek.setAttribute("value", currentStatus.currentTime); + + seek.addEventListener("change", function(event) { + browser.runtime.sendMessage({ + "command": "seek", + "seek": seek.value + }); + player.currentTime = seek.value; + currentTime.textContent = getTimeString(player.currentTime); + }); + + seekIntervals.push(setInterval(function() { + if (seek.value < currentStatus.duration) { + if (!player.paused || currentStatus.playing) { + seek.value++; + currentTime.textContent = getTimeString(seek.value); + } + } else { + seek.value = 0; + clearInterval(seekInterval); + } + }, 1000)); + + var audio = document.createElement("div"); + audio.id = "muffcast-audio"; + + var volume = document.createElement("input"); + volume.id = "muffcast-volume"; + volume.setAttribute("type", "range"); + volume.setAttribute("min", 0); + volume.setAttribute("max", 1); + volume.setAttribute("step", 0.01); + volume.setAttribute("value", status.muted ? 0 : status.volume); + + volume.addEventListener("change", function(event) { + browser.runtime.sendMessage({ + "command": "volume", + "volume": volume.value + }); + player.volume = volume.value; + player.muted = volume.value == 0; + }); + + var mute = document.createElement("a"); + mute.id = "muffcast-mute"; + mute.innerHTML = status.muted ? '' : (player.volume < 0.5 ? '' : ''); + mute.addEventListener("click", function(event) { + browser.runtime.sendMessage({ + "command": "mute", + "muted": !player.muted + }); + player.muted = !player.muted; + volume.value = player.muted ? 0 : player.volume; + mute.innerHTML = player.muted ? '' : (player.volume < 0.5 ? '' : ''); + }) + + var icon = document.createElement("span"); + icon.id = "muffcast-icon"; + icon.innerHTML = ''; + + overlay.appendChild(icon); + overlay.appendChild(play); + audio.appendChild(mute); + audio.appendChild(volume); + overlay.appendChild(audio); + overlay.appendChild(currentTime); + overlay.appendChild(seek); + overlay.appendChild(duration); + overlay.appendChild(stop); + + var castLink = document.getElementById("muffcast-cast-link_" + status.index); + castLink.classList.add("active"); + + syncIntervals.push(setInterval(function() { + // sync status + if (!player.isSyncInterval) { + getStatus().then(function(status) { + player.isSyncInterval = true; + + currentStatus.playing = status.playing; + currentStatus.currentTime = status.currentTime; + currentStatus.volume = status.volume; + currentStatus.muted = status.muted; + + currentTime.textContent = getTimeString(currentStatus.currentTime); + seek.value = currentStatus.currentTime + play.innerHTML = currentStatus.playing ? '' : ''; + volume.value = currentStatus.muted ? 0 : currentStatus.volume; + mute.innerHTML = currentStatus.muted ? '' : (currentStatus.volume < 0.5 ? '' : ''); + + player.volume = currentStatus.volume; + player.muted = currentStatus.muted; + player.currentTime = currentStatus.currentTime; + }) + } + }, syncIntervalTime)); + + setTimeout(function() { + player.addEventListener("play", function(event) { + if (status.playing && status.url == encodeURIComponent(window.location.href)) { + browser.runtime.sendMessage({ + "command": "pause", + "seek": player.currentTime + }); + play.innerHTML = ''; + status.playing = !status.playing; + } + }) + + player.addEventListener("pause", function(event) { + if (!status.playing && status.url == encodeURIComponent(window.location.href.href)) { + browser.runtime.sendMessage({ + "command": "play", + "seek": player.currentTime + }); + play.innerHTML = ''; + status.playing = !status.playing; + } + }) + + player.addEventListener("seeked", function(event) { + if (status.playing && status.url == encodeURIComponent(window.location.href)) { + if (!player.isSyncInterval) { + browser.runtime.sendMessage({ + "command": "seek", + "seek": player.currentTime + }); + seek.value = player.currentTime; + currentTime.textContent = getTimeString(player.currentTime); + } else { + player.isSyncInterval = false; + }; + } + }) + + player.addEventListener("volumechange", function(event) { + if (status.url == encodeURIComponent(window.location.href)) { + browser.runtime.sendMessage({ + "command": "volume", + "volume": player.volume + }); + volume.value = player.muted ? 0 : player.volume; + mute.innerHTML = player.muted ? '' : (player.volume < 0.5 ? '' : ''); + } + }) + }, 1500); + }) + } + }) +} + +browser.runtime.onMessage.addListener(function(message) { + var videos = document.getElementsByTagName("video"); + switch (message.command) { + case "update": + setStatus(); + break; + case "load": + var player = videos[message.index]; + player.pause(); + player.style.border = "none"; + browser.runtime.sendMessage({ + "command": "load", + "url": encodeURIComponent(window.location.href), + "type": "video", + "index": message.index, + "seek": player.currentTime, + "volume": player.volume, + "muted": player.muted + }); + break; + case "mark": + var player = videos[message.index]; + player.style.border = "5px solid red"; + break; + case "unmark": + var player = videos[message.index]; + player.style.border = "none"; + break; + } +}) diff --git a/options/options.html b/options/options.html new file mode 100644 index 0000000..073d0c5 --- /dev/null +++ b/options/options.html @@ -0,0 +1,20 @@ + + + + + + + + + +
+
+
+ +
+ + + + + + diff --git a/options/options.js b/options/options.js new file mode 100644 index 0000000..7f8a2ed --- /dev/null +++ b/options/options.js @@ -0,0 +1,19 @@ +function saveOptions(e) { + e.preventDefault(); + browser.storage.local.set({ + "muffcast": { + "url": document.querySelector("#muffcast-url").value, + "syncInterval": document.querySelector("#muffcast-sync-interval").value + } + }); +} + +function restoreOptions() { + browser.storage.local.get("muffcast").then(function(result) { + document.querySelector("#muffcast-url").value = result.muffcast && result.muffcast.url || "http://localhost:8128"; + document.querySelector("#muffcast-sync-interval").value = result.muffcast && result.muffcast.syncInterval || 30000; + }); +} + +document.addEventListener("DOMContentLoaded", restoreOptions); +document.querySelector("form").addEventListener("submit", saveOptions); diff --git a/popup/popup.css b/popup/popup.css new file mode 100644 index 0000000..5f5e5dc --- /dev/null +++ b/popup/popup.css @@ -0,0 +1,126 @@ +@font-face { + font-family: 'FontAwesome'; + src: url('" + browser.extension.getURL("fonts/fontawesome.eot") + "?v=4.7.0'); + src: url('" + browser.extension.getURL("fonts/fontawesome.eot") + "?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome.svg?v=4.7.0#fontawesomeregular') format('svg'); + font - weight: normal; + font - style: normal; +} + +* { + box-sizing: border-box; +} + +body { + font-family: 'OpenSans', 'Roboto', 'sans-serif', 'sans'; + max-width: 400px; + padding: 0; + margin: 0; +} + +.visible { + display: inline-block !important; +} + +.text-center { + text-align: center; +} + +a { + text-decoration: none; + color: #d13d29; +} + +a:hover { + color: #9e2e1f; +} + +#muffcast { + width: 400px; + background-color: #fefefe; + color: #444; + display: none; +} + +#idle, #error { + width: 100%; + display: none; + color: #d13d29; + padding: 15px; +} + +#title { + display: block; + color: #333; +} + +#title:hover {} + +#host { + display: block; + font-size: 0.9em; + color: #888; +} + +.icon { + color: #468ac3; +} + +.info { + display: block; + padding: 15px; +} + +.audio { + display: -moz-flex; + display: flex; +} + +#mute { + cursor: pointer; +} + +#volume { + flex-grow: 1; + height: 20px; +} + +#volume::-moz-range-track { + width: 100%; + height: 3px; + cursor: pointer; + background: #468ac3; + border-radius: 2px; + cursor: pointer; +} + +#volume::-moz-range-thumb { + box-shadow: 0px 0px 0px #d13d29; + border: 0px solid #d13d29; + height: 14px; + width: 14px; + border-radius: 50%; + background: #d13d29; + cursor: pointer; +} + +#volume::-moz-range-thumb:hover { + color: #9e2e1f; + height: 16px; + width: 16px; +} + +.bottom-menu { + display: block; + background-color: #efefef; + padding: 15px; +} + +#play { + float: left; + cursor: pointer; +} + +#stop { + float: right; + cursor: pointer; +} diff --git a/popup/popup.html b/popup/popup.html new file mode 100644 index 0000000..ddee42e --- /dev/null +++ b/popup/popup.html @@ -0,0 +1,39 @@ + + + + + + + + + + + +
Muffcast is ready!
+
+ + + + + Muffcast is not reachable! +

Please check settings and server.

+
+
+
+ + +
+ + +
+
+
+ + Stop Muffcast +
+
+
+ + + + diff --git a/popup/popup.js b/popup/popup.js new file mode 100644 index 0000000..97f9c02 --- /dev/null +++ b/popup/popup.js @@ -0,0 +1,114 @@ +var muffcastUrl = "http://localhost:8128"; +browser.storage.local.get("muffcast").then(function(result) { + muffcastUrl = result.muffcast && result.muffcast.url || muffcastUrl; +}) + +var clientUpdate = function() { + return browser.tabs.query({ + currentWindow: true, + active: true + }).then(function(tabs) { + var tab = tabs[0]; + return browser.tabs.sendMessage(tab.id, { + command: "update" + }); + }); +} + +var setStatus = function() { + var xhttp = new XMLHttpRequest(); + xhttp.open("GET", muffcastUrl, true); + xhttp.onreadystatechange = function() { + var idleElement = document.getElementById("idle"); + var errorElement = document.getElementById("error"); + var muffcastElement = document.getElementById("muffcast"); + if (this.readyState == 4) { + if (this.status == 200) { + var status = this.responseText && JSON.parse(this.responseText); + if (status && status.running) { + var titleElement = document.getElementById("title"); + titleElement.textContent = status.title; + titleElement.href = decodeURIComponent(status.url); + + var hostElement = document.getElementById("host"); + hostElement.textContent = decodeURIComponent(status.host); + + var muteElement = document.getElementById("mute"); + + var updateMuteElement = function() { + muteElement.innerHTML = status.muted ? '' : (status.volume < 0.5 ? '' : ''); + } + + muteElement.addEventListener("click", function(event) { + status.muted = !status.muted; + browser.runtime.sendMessage({ + "command": "mute", + "muted": status.muted + }); + volumeElement.value = status.muted ? 0 : status.volume; + updateMuteElement(); + clientUpdate(); + }) + + updateMuteElement(); + + var volumeElement = document.getElementById("volume"); + volumeElement.setAttribute("value", status.muted ? 0 : status.volume); + + volumeElement.addEventListener("change", function(event) { + browser.runtime.sendMessage({ + "command": "volume", + "volume": volumeElement.value + }); + status.volume = volumeElement.value; + status.muted = volumeElement.value == 0; + updateMuteElement(); + }); + + var playElement = document.getElementById("play"); + + playElement.innerHTML = status.playing ? '' : ''; + + playElement.addEventListener("click", function(event) { + browser.runtime.sendMessage({ + "command": status.playing ? "pause" : "play" + }); + status.playing = !status.playing; + playElement.innerHTML = status.playing ? '' : ''; + + clientUpdate(); + }) + + var stopElement = document.getElementById("stop"); + + stopElement.addEventListener("click", function(event) { + browser.runtime.sendMessage({ + "command": "stop" + }); + clientUpdate(); + muffcastElement.classList.remove("visible"); + idleElement.classList.add("visible"); + errorElement.classList.remove("visible"); + }) + + muffcastElement.classList.add("visible"); + idleElement.classList.remove("visible"); + errorElement.classList.remove("visible"); + + } else { + muffcastElement.classList.remove("visible"); + idleElement.classList.add("visible"); + errorElement.classList.remove("visible"); + } + } else { + muffcastElement.classList.remove("visible"); + idleElement.classList.remove("visible"); + errorElement.classList.add("visible"); + } + } + } + xhttp.setRequestHeader("Content-type", "application/json"); + xhttp.send(); +} + +setStatus();