Compare commits

...

12 Commits

Author SHA1 Message Date
Christian Richter
e54c6e8b72 reenable amd64
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-10-16 17:55:35 +02:00
Christian Richter
b9f505a64e disable amd64 builds
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-08-29 10:44:30 +02:00
Christian Richter
904a96dcbb fix missing s in yt api link
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-08-01 08:33:09 +02:00
Christian Richter
aaf3b71b7c fix git url
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 16:56:53 +02:00
Christian Richter
4840619908 minor cleanups
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 16:46:59 +02:00
Christian Richter
8bc0539032 update layout, extract js code & css from html
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 12:50:28 +02:00
Christian Richter
4510449225 next attempt
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:55:52 +02:00
Christian Richter
cb696853f5 try to fix broken remove song
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:40:36 +02:00
Christian Richter
fa5216c8d4 add secure protocol
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:19:05 +02:00
Christian Richter
4e13c78c5a test cross origin for websocket domain
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 10:08:58 +02:00
Christian Richter
a023fbed87 add listener count to ws message
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 09:03:37 +02:00
Christian Richter
27916b6356 make domains configurable & extend webserver
Some checks reported errors
continuous-integration/drone/pr Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 09:01:41 +02:00
7 changed files with 274 additions and 191 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
**/*.json
node_modules
.drone.yml
assets/baseurl.js

View File

@@ -1,6 +1,6 @@
FROM node:latest
LABEL maintainer="dragonchaser <weirdradio@datenschmutz.space>"
RUN git clone https://github.com/dragonchaser/weirdradio /weirdradio
RUN git clone https://git.wardrum.de/dragonchaser/weirdradio /weirdradio
WORKDIR /weirdradio
RUN npm install
CMD node weirdradio

View File

@@ -2,200 +2,28 @@
<head>
<script
type="text/javascript"
src="http://www.youtube.com/player_api"
src="https://www.youtube.com/player_api"
></script>
<script type="text/javascript" src="baseurl.js"></script>
<script type="text/javascript" src="logic.js"></script>
<link rel="stylesheet" href="style.css" />
<title>Weirdradio</title>
</head>
<style>
div.body {
width: 1024px;
height: 768px;
border: solid 1px;
padding: 1pc;
}
div.leftcolumn {
width: 30%;
float: left;
}
div.title {
height: 20%;
}
div.playlist {
height: 80%;
}
div.rightcolumn {
width: 60%;
float: left;
}
#playerframe {
height: 100%;
width: 100%;
background-color: purple;
}
</style>
<body onload="connectSocket()">
<div class="body">
<div class="leftcolumn">
<div class="title">
<h1>Weirdradio</h1>
Weirdradio
</div>
<div id="playlist" class="playlist"></div>
</div>
<div class="rightcolumn">
<div class="listeners">Listeners: <span id="listeners">1</span></div>
<div class="playerframe">
<div id="player"></div>
</div>
<div class="playlistframe">
<div class="title">
Playlist
</div>
<div id="playlist" class="playlist"></div>
</div>
</div>
</body>
<script>
var playlist = [];
var socketConnector = null;
var socketSemaphore = false;
var tag = document.createElement("script");
var currentVideoID = null;
var player = null;
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
currentVideoID = "Wch3gJG2GJ4";
player = new YT.Player("player", {
height: "390",
width: "640",
videoId: currentVideoID,
playerVars: {
playsinline: 1,
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == 0) {
console.log("playing stopped");
removeFromPlaylist(currentVideoID);
playNext();
}
//if (event.data == YT.PlayerState.PLAYING && !done) {
// setTimeout(stopVideo, 6000);
// done = true;
//}
}
function stopVideo() {
player.stopVideo();
}
function connectSocket() {
if (socketSemaphore) {
console.log(
"Another connection attempt already in progress, canceling!"
);
return;
}
console.log("Connecting to socket");
socketSemaphore = true;
baseUrl = window.location.host.replace("8080", "8090");
let socket = new WebSocket("ws://" + baseUrl);
socket.onopen = function (e) {
clearInterval(socketConnector);
socketConnector = null;
socketSemaphore = false;
console.log("[open] Connection established");
};
// TODO: on close and on error attempt reconnect
socket.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
// TODO: add some nice ui thingi to show reconnect
// TODO: also add some timeout function to repeat this until connect is successful, let it interact with 'onopen'
console.log("[close] Connection died, attempting reconnect");
if (!socketConnector) {
socketConnector = setInterval(connectSocket, 5000);
socketSemaphore = false;
}
}
};
socket.onmessage = function (event) {
data = JSON.parse(event.data);
if (data["videoId"] != undefined && data["videoId"] != null) {
addToPlaylist(data);
console.log(`[message] Data received from server: ${event.data}`);
} else {
console.log(
`[unparseable message] Data received from server ${event.data}`
);
}
};
socketSemaphore = false;
}
function removeFromPlaylist(videoId) {
tmp = [];
playlist.forEach((item) => {
if (item.videoId != videoId) {
tmp.push(item);
} else {
console.log(`removing ${videoId}`);
}
});
playlist = tmp;
// remove from DOM
rm = document.getElementById(videoId);
if (rm != null) {
rm.remove();
}
currentVideoID = null;
}
function playNext() {
console.log(playlist)
console.log(playlist[0].videoId)
if (playlist[0] != null && playlist[0] != "") {
player.loadVideoById(playlist[0].videoId);
player.playVideo();
}
}
function addToPlaylist(data) {
if (document.getElementById(data.videoId) == null) {
playlist.push(data);
pl = document.getElementById(
"playlist"
).innerHTML += `<div id="${data.videoId}"><div>${data.title}</div><div><a target="_blank" href="${data.link}">${data.link}</a></div></div>`;
if (player.getPlayerState() == YT.PlayerState.ENDED) {
player.loadVideoById(data["videoId"]);
player.playVideo();
currentVideoID = data["videoId"];
}
} else {
console.log(`item ${data.videoId} already exists in playlist skipping`);
}
}
//function myDebugger() {
// console.log(socketSemaphore);
// console.log(socketConnector);
// console.log("=================");
//}
//myDebugger = setInterval(myDebugger, 2000);
</script>
</html>

142
assets/logic.js Normal file
View File

@@ -0,0 +1,142 @@
var playlist = [];
var socketConnector = null;
var socketSemaphore = false;
var tag = document.createElement("script");
var currentVideoID = null;
var player = null;
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
currentVideoID = "tbnLqRW9Ef0";
player = new YT.Player("player", {
//height: "390",
//width: "100%",
videoId: currentVideoID,
playerVars: {
playsinline: 1,
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == 0) {
playNext();
console.log("playing stopped");
}
//if (event.data == YT.PlayerState.PLAYING && !done) {
// setTimeout(stopVideo, 6000);
// done = true;
//}
}
function stopVideo() {
player.stopVideo();
}
function connectSocket() {
if (socketSemaphore) {
console.log("Another connection attempt already in progress, canceling!");
return;
}
console.log("Connecting to socket");
socketSemaphore = true;
let socket = new WebSocket(wsBaseUrl);
socket.onopen = function (e) {
clearInterval(socketConnector);
socketConnector = null;
socketSemaphore = false;
console.log("[open] Connection established");
};
// TODO: on close and on error attempt reconnect
socket.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
// TODO: add some nice ui thingi to show reconnect
// TODO: also add some timeout function to repeat this until connect is successful, let it interact with 'onopen'
console.log("[close] Connection died, attempting reconnect");
if (!socketConnector) {
socketConnector = setInterval(connectSocket, 5000);
socketSemaphore = false;
}
}
};
socket.onmessage = function (event) {
data = JSON.parse(event.data);
if (data["videoId"] != undefined && data["videoId"] != null) {
addToPlaylist(data);
updateListeners(data);
console.log(`[message] Data received from server: ${event.data}`);
} else {
console.log(
`[unparseable message] Data received from server ${event.data}`
);
}
};
socketSemaphore = false;
}
function updateListeners(data) {
document.getElementById("listeners").innerHTML = data.listeners;
}
function removeFromPlaylist(videoId) {
delete playlist[currentVideoID];
// remove from DOM
rm = document.getElementById(videoId);
if (rm != null) {
rm.remove();
}
currentVideoID = null;
}
function playNext() {
removeFromPlaylist(currentVideoID);
if (playlist[0] != null && playlist[0] != "") {
player.loadVideoById(playlist[0].videoId);
player.playVideo();
}
}
function addToPlaylist(data) {
if (document.getElementById(data.videoId) == null) {
playlist.push(data);
pl = document.getElementById(
"playlist"
).innerHTML += `<div class="playlistrow" id="${data.videoId}">
<div class="title">${data.title}</div>
<div class="ytlink">
<a target="_blank" href="${data.link}">${data.link}</a>
</div>
<div class="controls">
<div class="control" onclick="removeFromPlaylist('${data.videoId}')">Remove</div>
<div class="control">Play</div>
</div>
</div>`;
if (player.getPlayerState() == YT.PlayerState.ENDED) {
player.loadVideoById(data["videoId"]);
player.playVideo();
currentVideoID = data["videoId"];
}
} else {
console.log(`item ${data.videoId} already exists in playlist skipping`);
}
}

79
assets/style.css Normal file
View File

@@ -0,0 +1,79 @@
body {
padding: 0;
margin: 0;
}
div.body {
position: relative;
width: 100%;
padding: 0;
margin: 0;
}
div.body > div.title {
padding: 1pc;
font-size: 2pc;
font-weight: bold;
float: left;
width: 50%;
}
div.body > div.listeners {
float: right;
width: 20%;
padding: 2pc 2pc 0 0;
font-size: xx-small;
font-weight: bold;
text-align: right;
}
div.playlistframe {
}
div.playlistframe > div.title {
padding: 1pc;
font-size: 1.5pc;
font-weight: bold;
}
div.playlist {
padding: 0;
}
div.playlist > div.playlistrow {
padding: 1pc;
position: relative;
}
div.playlist > div.playlistrow:nth-child(even) {
background: #fff;
}
div.playlist > div.playlistrow:nth-child(odd) {
background: #ddd;
}
div.playlist > div.playlistrow > div.title{
font-weight: bold;
font-size: medium;
}
div.playlist > div.playlistrow > div.ytlink{
font-size: xx-small;
}
div.playlist > div.playlistrow > div.controls {
position: absolute;
top: 0;
right: 0;
}
div.playlist > div.playlistrow > div.controls > div.control {
float: right;
background-color: #aaa;
margin: 1pc 0.5pc 0 0;
cursor: pointer;
}
div.playerframe {
}
#player {
width: 100%;
}

View File

@@ -1,6 +1,9 @@
{
"homeServerUrl": "https://matrix.your-homeserver.org",
"accessToken": "youraccesstokencanbeobtainedthroughriot",
"domain": "yourdomain.tld",
"webSocketDomain": "ws.yourdomain.tld",
"secure":true,
"storage": "config/bot.json",
"assetDir": "assets/",
"webServerPort": "8080",

View File

@@ -20,14 +20,27 @@ const client = new MatrixClient(
new SimpleFsStorageProvider(config.storage)
);
// write javascript for baseurl
if (config.secure) {
urlConfigData = `var baseUrl = "https://${config.domain}";\nvar wsBaseUrl = "wss://${config.webSocketDomain}";\n`;
} else {
urlConfigData = `var baseUrl = "http://${config.domain}";\nvar wsBaseUrl = "ws://${config.webSocketDomain}";\n`;
}
fs.writeFileSync(
config.assetDir + "/baseurl.js",
urlConfigData,
function (err) {
if (err) return console.log(err);
}
);
// load assets
console.log("Reading assets...")
console.log("Reading assets...");
let assets = [];
var files = fs.readdirSync(config.assetDir);
files.forEach((name) => {
assets[name] = fs.readFileSync("assets/" + name);
});
console.log("[DONE]")
console.log("[DONE]");
// create server, this is for delivering the iframe page
const webServer = http
.createServer((req, res) => {
@@ -36,7 +49,23 @@ const webServer = http
reqFileName = "index.html";
}
if (assets[reqFileName] != undefined && assets[reqFileName] != null) {
res.writeHead(200, { "Content-Type": "text/html" });
extension = reqFileName.split(".").pop();
if (extension == "html" || extension == "html") {
res.writeHead(200, {
"Content-Type": "text/html",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
} else if (extension == "js") {
res.writeHead(200, {
"Content-Type": "text/javascript",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
} else {
res.writeHead(200, {
"Content-TYpe": "text/plain",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
}
res.end(assets[reqFileName]);
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
@@ -85,7 +114,7 @@ client.on("room.message", (roomId, event) => {
//link_matches = body.match(/https?:\/\/[^\ ]*youtu[^\ ]*/g);
var r = new RegExp(/https?:\/\/[^\ ]*youtube.com\/watch\?v=([^\ ]*)/g);
link_matches = r.exec(body);
if (link_matches && link_matches.length > 1) {
if (link_matches && link_matches.length > 1 && link_matches[0] != null) {
// get title
request(link_matches[0], function (err, _res, body) {
if (err) return console.error(err);
@@ -100,6 +129,7 @@ client.on("room.message", (roomId, event) => {
link: link_matches[0],
videoId: link_matches[1],
title: title,
listeners: sockets.length,
};
console.log("Relaying: " + obj.link);
sockets.forEach((s) => s.send(JSON.stringify(obj)));