initial commit
This commit is contained in:
commit
8a890a8cd1
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -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.
|
49
README.md
Normal file
49
README.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## Server
|
||||||
|
This is the server extension. Install on a device for playing HTML5 Videos in full-screen controlled by **Muffcast Client**s.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- Firefox/Browser instance
|
||||||
|
- Internet access
|
||||||
|
|
||||||
|
To benefit of this extension, **Muffcast Client** is required on other Firefox/Browser instance in the same network.
|
||||||
|
|
||||||
|
### Firefox/Browser Setup
|
||||||
|
For full-screen support, following setup in Firefox is required
|
||||||
|
In *about:config* `full-screen-api.allow-trusted-requests-only = false`
|
||||||
|
|
||||||
|
On stopped playback, images from unsplash.com are loaded. If you have an unsplash API account, feel free to enter you API-key and credit name for Hotlinking.
|
||||||
|
|
||||||
|
### System Setup
|
||||||
|
Native application *muffcast.py* is required for working properly with client extension.
|
||||||
|
Setup for native application as described here https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging
|
||||||
|
|
||||||
|
*muffcast.json*
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "muffcast",
|
||||||
|
"path": *path to muffcast.py*,
|
||||||
|
"type": "stdio",
|
||||||
|
"allowed_extensions": ["muffcast@champonthis.de"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional Raspberry Pi Configuration
|
||||||
|
- ARCH Linux
|
||||||
|
- Matchbox Window Manager
|
||||||
|
- Firefox with Extension
|
||||||
|
|
||||||
|
Raspberry Pi is limited on playing HTML5 videos in browser. Do not expect to play 1080p videos smoothly.
|
||||||
|
|
||||||
|
###### 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.)
|
92
background/server.js
Normal file
92
background/server.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
console.log("muffcast server v0.1");
|
||||||
|
|
||||||
|
var port = browser.runtime.connectNative("muffcast");
|
||||||
|
var waitForLoad = false;
|
||||||
|
|
||||||
|
browser.windows.getCurrent().then(function(windowInfo) {
|
||||||
|
///*
|
||||||
|
browser.windows.update(windowInfo.id, {
|
||||||
|
state: "fullscreen"
|
||||||
|
});
|
||||||
|
//*/
|
||||||
|
///*
|
||||||
|
browser.tabs.query({
|
||||||
|
currentWindow: true,
|
||||||
|
active: true
|
||||||
|
}).then(function(tabs) {
|
||||||
|
var tab = tabs[0];
|
||||||
|
browser.tabs.update(tab.id, {
|
||||||
|
url: browser.extension.getURL("splash/splash.html")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//*/
|
||||||
|
})
|
||||||
|
|
||||||
|
var sendCommand = function(message) {
|
||||||
|
browser.tabs.query({
|
||||||
|
currentWindow: true,
|
||||||
|
active: true
|
||||||
|
}).then(function(tabs) {
|
||||||
|
for (let tab of tabs) {
|
||||||
|
browser.tabs.sendMessage(
|
||||||
|
tab.id, message
|
||||||
|
).catch(function(error) {
|
||||||
|
port.postMessage(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Listen for messages from the app.
|
||||||
|
*/
|
||||||
|
port.onMessage.addListener(function(message) {
|
||||||
|
console.log("received", message);
|
||||||
|
if (message.command) {
|
||||||
|
if (message.command == "load") {
|
||||||
|
if (message && message.url) {
|
||||||
|
browser.tabs.query({
|
||||||
|
currentWindow: true,
|
||||||
|
active: true
|
||||||
|
}).then(function(tabs) {
|
||||||
|
var tab = tabs[0];
|
||||||
|
waitForLoad = message;
|
||||||
|
if (tab.url != decodeURIComponent(message.url)) {
|
||||||
|
browser.tabs.update(tab.id, {
|
||||||
|
url: decodeURIComponent(message.url)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendCommand(message);
|
||||||
|
waitForLoad = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
if (message.command == "stop") {
|
||||||
|
browser.tabs.query({
|
||||||
|
currentWindow: true,
|
||||||
|
active: true
|
||||||
|
}).then(function(tabs) {
|
||||||
|
var tab = tabs[0];
|
||||||
|
waitForLoad = false;
|
||||||
|
browser.tabs.update(tab.id, {
|
||||||
|
url: browser.extension.getURL("splash/splash.html")
|
||||||
|
}).then(function() {
|
||||||
|
port.postMessage("ok");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendCommand(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(function(message) {
|
||||||
|
if (message !== "loaded") {
|
||||||
|
console.log("response", message);
|
||||||
|
port.postMessage(message);
|
||||||
|
} else if (waitForLoad) {
|
||||||
|
sendCommand(waitForLoad);
|
||||||
|
waitForLoad = false;
|
||||||
|
}
|
||||||
|
})
|
BIN
fonts/FontAwesome.otf
Normal file
BIN
fonts/FontAwesome.otf
Normal file
Binary file not shown.
BIN
fonts/fontawesome.eot
Normal file
BIN
fonts/fontawesome.eot
Normal file
Binary file not shown.
2671
fonts/fontawesome.svg
Normal file
2671
fonts/fontawesome.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
BIN
fonts/fontawesome.ttf
Normal file
BIN
fonts/fontawesome.ttf
Normal file
Binary file not shown.
BIN
fonts/fontawesome.woff
Normal file
BIN
fonts/fontawesome.woff
Normal file
Binary file not shown.
BIN
fonts/fontawesome.woff2
Normal file
BIN
fonts/fontawesome.woff2
Normal file
Binary file not shown.
BIN
icons/muffcast-server-32-light.png
Normal file
BIN
icons/muffcast-server-32-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 206 B |
BIN
icons/muffcast-server-32.png
Normal file
BIN
icons/muffcast-server-32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 213 B |
BIN
icons/muffcast-server-48.png
Normal file
BIN
icons/muffcast-server-48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 B |
48
manifest.json
Normal file
48
manifest.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"description": "Cast HTML5 video to other Firefox instance. This is the server extension. Install on a device for playing HTML5 Videos in fullscreen. A native application is needed to listen to client commands. Read the README.txt for additonal setup information. On stopped playpack, images from unsplash.com are loaded. If you have an unsplah API account, feel free to enter you API-key.",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Muffcast Server",
|
||||||
|
"version": "0.1.3",
|
||||||
|
"homepage_url": "https://www.champonthis.de/projects/muffcast",
|
||||||
|
"icons": {
|
||||||
|
"48": "icons/muffcast-server-48.png"
|
||||||
|
},
|
||||||
|
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "muffcast@champonthis.de"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background/server.js"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"permissions": [
|
||||||
|
"activeTab",
|
||||||
|
"nativeMessaging",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
|
||||||
|
"content_scripts": [{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"js": ["muffcast-server.js"]
|
||||||
|
}],
|
||||||
|
|
||||||
|
"options_ui": {
|
||||||
|
"page": "options/options.html",
|
||||||
|
"browser_style": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"content_security_policy": "script-src 'self'; object-src 'self'; img-src 'self' https://source.unsplash.com https://images.unsplash.com;",
|
||||||
|
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"splash/splash.html",
|
||||||
|
"fonts/iAWriterDuospace-Regular.eot",
|
||||||
|
"fonts/iAWriterDuospace-Regular.otf",
|
||||||
|
"fonts/iAWriterDuospace-Regular.svg",
|
||||||
|
"fonts/iAWriterDuospace-Regular.ttf",
|
||||||
|
"fonts/iAWriterDuospace-Regular.woff"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
156
muffcast-server.js
Normal file
156
muffcast-server.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
console.log("muffcast server v0.1");
|
||||||
|
|
||||||
|
var player;
|
||||||
|
|
||||||
|
var loaded = function() {
|
||||||
|
if (document.readyState === 'complete') {
|
||||||
|
setTimeout(function() {
|
||||||
|
browser.runtime.sendMessage("loaded");
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded();
|
||||||
|
|
||||||
|
document.onreadystatechange = function() {
|
||||||
|
loaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
var getPlayer = function(message) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var host = window.location.hostname;
|
||||||
|
switch (host) {
|
||||||
|
case "www.zdf.de":
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementsByClassName("zdfplayer-start-icon")[0].click();
|
||||||
|
resolve(document.getElementsByTagName(message.type)[message.index]);
|
||||||
|
}, 500)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
resolve(document.getElementsByTagName(message.type)[message.index]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(function(message) {
|
||||||
|
console.log("client", message);
|
||||||
|
switch (message.command) {
|
||||||
|
case "status":
|
||||||
|
var status = {
|
||||||
|
"api": "muffcast - server v0.1",
|
||||||
|
"running": false
|
||||||
|
}
|
||||||
|
if (player) {
|
||||||
|
status.running = true;
|
||||||
|
status.paused = player.paused;
|
||||||
|
status.playing = !player.paused;
|
||||||
|
status.currentTime = player.currentTime;
|
||||||
|
status.volume = player.volume;
|
||||||
|
status.muted = player.muted;
|
||||||
|
status.duration = player.duration;
|
||||||
|
status.index = player.index;
|
||||||
|
status.type = player.tagName;
|
||||||
|
status.url = encodeURIComponent(window.location.href);
|
||||||
|
status.host = encodeURIComponent(window.location.hostname);
|
||||||
|
status.title = document.title;
|
||||||
|
}
|
||||||
|
browser.runtime.sendMessage(status);
|
||||||
|
break;
|
||||||
|
case "load":
|
||||||
|
if (message.index >= 0) {
|
||||||
|
getPlayer(message).then(function(response) {
|
||||||
|
player = response;
|
||||||
|
if (player) {
|
||||||
|
player.currentTime = message.seek;
|
||||||
|
player.volume = message.volume;
|
||||||
|
player.muted = message.muted;
|
||||||
|
player.play();
|
||||||
|
player.index = message.index;
|
||||||
|
player.loaded = false;
|
||||||
|
player.addEventListener("canplay", function(event) {
|
||||||
|
if (!player.loaded) {
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
player.loaded = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// DEBUG TIMEOUT FOR FOCUS
|
||||||
|
setTimeout(function() {
|
||||||
|
if (player.requestFullscreen) {
|
||||||
|
player.requestFullscreen();
|
||||||
|
} else if (player.mozRequestFullScreen) {
|
||||||
|
player.mozRequestFullScreen();
|
||||||
|
} else if (player.webkitRequestFullScreen) {
|
||||||
|
player.webkitRequestFullScreen();
|
||||||
|
} else if (player.msRequestFullscreen) {
|
||||||
|
player.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player found for given index");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no index specified");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "play":
|
||||||
|
if (player) {
|
||||||
|
if (message.seek) {
|
||||||
|
player.currentTime = message.seek;
|
||||||
|
}
|
||||||
|
player.play();
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player loaded");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "pause":
|
||||||
|
if (player) {
|
||||||
|
if (message.seek) {
|
||||||
|
player.currentTime = message.seek;
|
||||||
|
}
|
||||||
|
player.pause();
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player loaded");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "seek":
|
||||||
|
if (player) {
|
||||||
|
if (message.seek) {
|
||||||
|
player.currentTime = message.seek;
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no seek specified");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player loaded");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "volume":
|
||||||
|
if (player) {
|
||||||
|
if (message.volume) {
|
||||||
|
player.volume = message.volume;
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no volume specified");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player loaded");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
if (player) {
|
||||||
|
player.muted = message.muted;
|
||||||
|
browser.runtime.sendMessage("ok");
|
||||||
|
} else {
|
||||||
|
browser.runtime.sendMessage("no player loaded");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
browser.runtime.sendMessage("unknown command");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
54
muffcast.py
Executable file
54
muffcast.py
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
|
||||||
|
server_address = ('',8128)
|
||||||
|
timeout = 15
|
||||||
|
|
||||||
|
# Send an encoded message to stdout.
|
||||||
|
def extensionCall(messageContent):
|
||||||
|
sys.stdout.write(struct.pack('@I', len(messageContent)))
|
||||||
|
sys.stdout.write(messageContent)
|
||||||
|
sys.stdout.flush()
|
||||||
|
# wait for extension response
|
||||||
|
i, o, e = select.select( [sys.stdin], [], [], timeout)
|
||||||
|
if (i):
|
||||||
|
rawLength = sys.stdin.read(4)
|
||||||
|
if len(rawLength) == 0:
|
||||||
|
return True
|
||||||
|
messageLength = struct.unpack('@I', rawLength)[0]
|
||||||
|
message = sys.stdin.read(messageLength)
|
||||||
|
return message
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
class PostHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
response = extensionCall('{"command" : "status"}')
|
||||||
|
if response:
|
||||||
|
self.send_response(200)
|
||||||
|
else:
|
||||||
|
self.send_response(500)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(response)
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
buffer = self.rfile.read(int(self.headers['Content-Length']))
|
||||||
|
response = extensionCall(buffer)
|
||||||
|
if response:
|
||||||
|
self.send_response(200)
|
||||||
|
else:
|
||||||
|
self.send_response(500)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(response)
|
||||||
|
|
||||||
|
httpd = HTTPServer(server_address, PostHTTPRequestHandler)
|
||||||
|
httpd.serve_forever()
|
22
options/options.html
Normal file
22
options/options.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<label>Unsplash Interval <input type="number" min="2000" step="1000" value="8000" id="muffcast-unsplash-interval" /></label> <br />
|
||||||
|
<label>Unsplash Credit <input type="text" id="muffcast-unsplash-credit" /></label> <br />
|
||||||
|
<label>Unsplash Client Id <input type="text" id="muffcast-unsplash-client" /></label> <br />
|
||||||
|
<button type="submit" class="browser-style">Save</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="options.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
21
options/options.js
Normal file
21
options/options.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
function saveOptions(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
browser.storage.local.set({
|
||||||
|
"muffcast": {
|
||||||
|
"unsplashInterval": document.querySelector("#muffcast-unsplash-interval").value,
|
||||||
|
"unsplashCredit": document.querySelector("#muffcast-unsplash-credit").value,
|
||||||
|
"unsplashClient": document.querySelector("#muffcast-unsplash-client").value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreOptions() {
|
||||||
|
browser.storage.local.get("muffcast").then(function(result) {
|
||||||
|
document.querySelector("#muffcast-unsplash-interval").value = result.muffcast && result.muffcast.unsplashInterval || 8000;
|
||||||
|
document.querySelector("#muffcast-unsplash-credit").value = result.muffcast && result.muffcast.unsplashCredit || "";
|
||||||
|
document.querySelector("#muffcast-unsplash-client").value = result.muffcast && result.muffcast.unsplashClient || "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||||
|
document.querySelector("form").addEventListener("submit", saveOptions);
|
2936
splash/font-awesome.css
vendored
Normal file
2936
splash/font-awesome.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
splash/splash.css
Normal file
60
splash/splash.css
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'OpenSans', 'Roboto', 'sans-serif', 'sans';
|
||||||
|
background: #000;
|
||||||
|
color: #d13d29;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 15px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 i.fa {
|
||||||
|
color: #468ac3;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:hover {
|
||||||
|
color: #468ac3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#background {
|
||||||
|
z-index: 1;
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
-webkit-background-size: cover;
|
||||||
|
-moz-background-size: cover;
|
||||||
|
-o-background-size: cover;
|
||||||
|
background-size: cover;
|
||||||
|
-webkit-transition: all 1.5s ease-in-out;
|
||||||
|
-moz-transition: all 1.5s ease-in-out;
|
||||||
|
-o-transition: all 1.5s ease-in-out;
|
||||||
|
transition: all 1.5s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
box-shadow: 0px -150px 150px 0px #000 inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsplash {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#description {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
21
splash/splash.html
Normal file
21
splash/splash.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="font-awesome.css" />
|
||||||
|
<link rel="stylesheet" href="splash.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1><i class="fa fa-television"></i> Muffcast is ready!</h1>
|
||||||
|
<div id="background">
|
||||||
|
<div class="unsplash">
|
||||||
|
<span id="description"></span>
|
||||||
|
<span id="user-meta">Photo by <a id="user" target="_blank"></a> / </span><a id="unsplash" target="_blank" href="https://unsplash.com/?utm_source=muffcast&utm_medium=referral&utm_campaign=api-credit">Unsplash</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="splash.js"></script>
|
||||||
|
<script src="../muffcast-server.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
107
splash/splash.js
Normal file
107
splash/splash.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
browser.tabs.query({
|
||||||
|
currentWindow: true,
|
||||||
|
active: true
|
||||||
|
}).then(function(tabs) {
|
||||||
|
var tab = tabs[0];
|
||||||
|
browser.tabs.insertCSS(tab.id, {
|
||||||
|
code: "@font-face { font-family: 'duospace';" +
|
||||||
|
"src: url('" + browser.extension.getURL("fonts/iAWriterDuospace-Regular.eot") + "');" +
|
||||||
|
"src: url('" + browser.extension.getURL("fonts/iAWriterDuospace-Regular.eot") + "?#iefix&v=4.7.0') format('embedded-opentype')," +
|
||||||
|
"url('" + browser.extension.getURL("fonts/iAWriterDuospace-Regular.woff") + "') format('woff')," +
|
||||||
|
"url('" + browser.extension.getURL("fonts/iAWriterDuospace-Regular.ttf") + "') format('truetype')," +
|
||||||
|
"url('" + browser.extension.getURL("fonts/iAWriterDuospace-Regular.svg") + "') format('svg');" +
|
||||||
|
"font - weight: normal;" +
|
||||||
|
"font - style: normal;}"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
var interval = 8000;
|
||||||
|
|
||||||
|
var unsplash = {};
|
||||||
|
|
||||||
|
browser.storage.local.get("muffcast").then(function(result) {
|
||||||
|
interval = result.muffcast && result.muffcast.unsplashInterval || interval;
|
||||||
|
unsplash.clientId = result.muffcast && result.muffcast.unsplashClient;
|
||||||
|
unsplash.credit = result.muffcast && result.muffcast.unsplashCredit;
|
||||||
|
|
||||||
|
setInterval(getUnsplash, interval);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
var splash = {
|
||||||
|
url: "",
|
||||||
|
user: "",
|
||||||
|
userUrl: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var getUnsplashRandom = function() {
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.open("GET", "https://source.unsplash.com/random/1600x900", true);
|
||||||
|
xhttp.addEventListener("load", function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
splash.url = this.responseURL;
|
||||||
|
delete splash.description;
|
||||||
|
delete splash.user;
|
||||||
|
delete splash.userUrl;
|
||||||
|
setBackground();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
var getUnsplash = function() {
|
||||||
|
var background = document.getElementById("background");
|
||||||
|
background.style.opacity = 0;
|
||||||
|
if (unsplash && unsplash.clientId && unsplash.credit) {
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.open("GET", "https://api.unsplash.com/photos/random?w=1600&h=900&client_id=" + unsplash.clientId, true);
|
||||||
|
xhttp.addEventListener("load", function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if (this.status == 200) {
|
||||||
|
var response = this.responseText ? JSON.parse(this.responseText) : false;
|
||||||
|
if (response) {
|
||||||
|
splash.url = response.urls.custom;
|
||||||
|
splash.description = response.description;
|
||||||
|
splash.user = response.user.name;
|
||||||
|
splash.userUrl = response.user.links.html + "?utm_source=" + unsplash.credit + "&utm_medium=referral&utm_campaign=api-credit";
|
||||||
|
setBackground();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getUnsplashRandom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xhttp.setRequestHeader("content-type", "application/json");
|
||||||
|
xhttp.send();
|
||||||
|
} else {
|
||||||
|
getUnsplashRandom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var setBackground = function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
var background = document.getElementById("background");
|
||||||
|
background.style['background-image'] = "url('" + splash.url + "')";
|
||||||
|
background.style.opacity = 1;
|
||||||
|
var userMeta = document.getElementById("user-meta");
|
||||||
|
if (splash.user) {
|
||||||
|
userMeta.classList.remove("hidden");
|
||||||
|
var user = document.getElementById("user");
|
||||||
|
user.href = splash.userUrl;
|
||||||
|
user.innerHTML = splash.user;
|
||||||
|
} else {
|
||||||
|
userMeta.classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
var description = document.getElementById("description");
|
||||||
|
if (splash.description) {
|
||||||
|
description.innerHTML = splash.description;
|
||||||
|
} else {
|
||||||
|
description.innerHTML = "";
|
||||||
|
}
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnsplash();
|
Reference in New Issue
Block a user