Skip to content

Instantly share code, notes, and snippets.

@RealityRipple
Last active February 14, 2022 03:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RealityRipple/ac04ad2efe3ea55d224defedcb4b0262 to your computer and use it in GitHub Desktop.
Save RealityRipple/ac04ad2efe3ea55d224defedcb4b0262 to your computer and use it in GitHub Desktop.
Track Display for Streamer Song List
<!doctype html>
<html>
<head>
<script>
var userID = 'your_channel'; /* twitch channel, lowercase */
var index = 1; /* queue index, starting from 1 for the next song and counting up */
var fadePad = 32; /* left and right margin */
var scrollRate = 20; /* lower is faster */
var interval = 2; /* update time in seconds */
var fadeTime = 0.5; /* fade speed in seconds */
var normPad = 8; /* label padding. must equal #label's left and right padding value */
var scrollPad = 6; /* padding between label and track name when scrolling */
var label = 'Next Up:'; /* use '' for no label */
var fitScroll = false; /* set to true to scroll text that fits */
var fitScrollRate = 15; /* lower is faster */
var trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'; /* display of below variables */
var artistDisp = '%ARTIST% - '; /* artist name display (with text shown when an artist is provided, empty if no artist or live learn) */
var titleDisp = '%TITLE%'; /* title display */
var userDisp = ' for %USER%'; /* requester username display (with text shown when a song is by request, empty if no requester) */
var emptyOpenDisp = 'The queue is empty! %TYPE% can add a song with the !sr command.' /* message to display when the queue does not have enough songs to reach this index and song requests are open */
var emptyClosedDisp = 'None! Last Song'; /* message to display when the queue does not have enough songs to reach this index and song requests are closed */
/*
* Example Track Displays:
*
* trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'
* artistDisp = '%ARTIST% - '
* titleDisp = '%TITLE%'
* userDisp = 'for %USER'
* => Pink Floyd - Wish You Were Here for RealityRipple
*
* trackDisp = '%DISP_USER%%DISP_TITLE%%DISP_ARTIST%'
* artistDisp = ' by %ARTIST%'
* titleDisp = '%TITLE%'
* userDisp = '%USER%\'s Request: '
* => RealityRipple's Request: Wish You Were Here by Pink Floyd
*
* trackDisp = '%DISP_TITLE%'
* artistDisp = '';
* titleDisp = '%TITLE%';
* userDisp = '';
* => Wish You Were Here
*/
var trackStyle = {
'style1': {title: 'Stairway to Heaven'},
'style2': {artist: 'Led Zeppelin'},
'style3': {attribute: '70s Rock'},
'style4': {livelearn: true},
'style5': {user: 'RealityRipple'}
};
</script>
<style>
.text
{
font-size: 32px;
line-height: 36px; /* adjust as needed for the text-shadow */
color: #FFFFFF;
text-shadow: 2px 2px 3px #000000;
/* font-family: 'Comic Sans MS'; */
}
.style1
{
color: #FFFF00;
}
.style2
{
font-style: italic;
}
.style3
{
font-weight: bold;
background: linear-gradient(to top, #FF0000, #FFFF00);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none;
}
.style4
{
color: #00FFFF;
}
.style5
{
color: #008000;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Track Display for StreamerSongList</title>
<style>
:root
{
--pos: 0px;
}
body
{
text-align: left;
margin: 0;
}
table
{
border: 0;
visibility: hidden;
position: fixed;
opacity: 0;
}
#label
{
margin: 0;
background-color: transparent;
padding: 2px 8px 8px;
white-space: nowrap;
}
#trackBox
{
width: 100%;
overflow: hidden;
position: relative;
}
.track
{
margin: 0;
top: 0;
display: none;
position: absolute;
background-color: transparent;
padding: 2px 0;
white-space: nowrap;
animation-name: fullWidth;
animation-timing-function: ease-in-out;
animation-direction: alternate;
animation-iteration-count: infinite;
visibility: hidden;
opacity: 0;
}
@keyframes fullWidth
{
from
{
transform: translate(0, 0);
}
to
{
transform: translate(var(--pos), 0);
}
}
</style>
<script>
var sTitle = false;
var sArtist = false;
var sUser = false;
var iAttr = [];
var lastW = 0;
var lastD = 0;
var attrs = {};
var queueOpen = false;
var queueMode = false;
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
function loadUser()
{
let p = new Promise((resolve, reject) =>
{
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID + '?platform=twitch', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseAttributes(x.responseText);
parseRequests(x.responseText);
resolve(true);
}
x.send(null);
}
);
return p;
}
function parseAttributes(sJSON)
{
attrs = {};
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('attributes'))
return;
if (!Array.isArray(j.attributes))
return;
for (let i = 0; i < j.attributes.length; i++)
{
if (!j.attributes[i].hasOwnProperty('id'))
continue;
if (!j.attributes[i].hasOwnProperty('name'))
continue;
attrs[j.attributes[i].id] = j.attributes[i].name;
}
}
function parseRequests(sJSON)
{
queueOpen = false;
queueMode = false;
let j = JSON.parse(sJSON);
if (j.hasOwnProperty('requestsActive'))
queueOpen = j.requestsActive;
if (j.hasOwnProperty('canAnonymousRequest') && j.canAnonymousRequest)
{
queueMode = 'Anyone';
return;
}
if (j.hasOwnProperty('canUserRequest') && j.canUserRequest)
{
queueMode = 'Users';
return;
}
if (j.hasOwnProperty('canFollowerRequest') && j.canFollowerRequest)
{
queueMode = 'Followers';
return;
}
if (j.hasOwnProperty('canSubscriberRequest') && j.canFollowerRequest)
{
queueMode = 'Subscribers';
return;
}
if (j.hasOwnProperty('canSubscriberT2Request') && j.canFollowerRequest)
{
queueMode = 'Tier 2 Subscribers';
return;
}
if (j.hasOwnProperty('canSubscriberT3Request') && j.canFollowerRequest)
{
queueMode = 'Tier 3 Subscribers';
return;
}
queueMode = 'Moderators';
}
function updateTrack()
{
sTitle = false;
sArtist = false;
sUser = false;
iAttr = [];
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID +'/queue', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseTrack(x.responseText);
displayTrack();
}
x.send(null);
}
function parseTrack(sJSON)
{
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('list') || !Array.isArray(j.list) || j.list.length < index + 1)
{
sTitle = false;
if (queueOpen)
{
if (emptyOpenDisp !== null && emptyOpenDisp !== '')
sTitle = emptyOpenDisp.replace('%TYPE%', queueMode);
}
else
{
if (emptyClosedDisp !== null && emptyClosedDisp !== '')
sTitle = emptyClosedDisp;
}
sArtist = false;
sUser = false;
iAttr = [];
return;
}
if (j.list[index].hasOwnProperty('requests') && Array.isArray(j.list[index].requests) && j.list[index].requests.length > 0)
{
let sUsers = [];
for (let i = 0; i < j.list[index].requests.length; i++)
{
if (!j.list[index].requests[i].hasOwnProperty('name') || j.list[index].requests[i].name === '')
continue;
sUsers.push(j.list[index].requests[i].name);
}
if (sUsers.length === 0)
sUser = false;
else
sUser = sUsers.join(', ');
}
if (j.list[index].hasOwnProperty('nonlistSong') && j.list[index].nonlistSong !== null && j.list[index].nonlistSong !== '')
{
sTitle = j.list[index].nonlistSong;
sArtist = false;
iAttr = ['livelearn'];
return;
}
else if (j.list[index].hasOwnProperty('song') && j.list[index].song !== null)
{
if (j.list[index].song.hasOwnProperty('title') && j.list[index].song.title !== '')
sTitle = j.list[index].song.title;
else
sTitle = false;
if (j.list[index].song.hasOwnProperty('artist') && j.list[index].song.artist !== '')
sArtist = j.list[index].song.artist;
else
sArtist = false;
iAttr = [];
if (j.list[index].song.hasOwnProperty('attributeIds') && Array.isArray(j.list[index].song.attributeIds))
{
for (let n = 0; n < j.list[index].song.attributeIds.length; n++)
{
iAttr.push(attrs[j.list[index].song.attributeIds[n]].toLowerCase());
}
}
}
}
async function showTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility === 'visible')
return;
tbl.style.opacity = '0';
tbl.style.visibility = 'visible';
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '1';
}
async function hideTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility !== 'visible')
return;
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '0';
await sleep(fadeTime * 1000);
tbl.style.transitionProperty = '';
tbl.style.transitionDuration = '';
tbl.style.visibility = '';
tbl.style.opacity = '';
}
function showFade()
{
let tb = document.getElementById('trackBox');
if (!tb.hasAttribute('style'))
tb.setAttribute('style', '-webkit-mask-image: linear-gradient(to right, transparent 0, black ' + fadePad + 'px, black calc(100% - ' + fadePad + 'px), transparent 100%);');
}
function hideFade()
{
let tb = document.getElementById('trackBox');
if (tb.hasAttribute('style'))
tb.removeAttribute('style');
}
async function showTrack()
{
let t = document.getElementById('track');
if (t.style.visibility === 'visible')
return;
t.style.visibility = 'visible';
t.style.opacity = '0';
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '1';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
function styleTrack(measureOnly)
{
if (trackStyle === false)
return;
for (let s in trackStyle)
{
if (!measureOnly)
{
if (document.getElementById('track').classList.contains(s))
document.getElementById('track').classList.remove(s);
}
if (document.getElementById('measure').classList.contains(s))
document.getElementById('measure').classList.remove(s);
if (trackStyle[s].hasOwnProperty('user'))
{
if (sUser === false)
continue;
if (!sUser.toLowerCase().split(', ').includes(trackStyle[s].user.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('title'))
{
if (sTitle === false)
continue;
if (sTitle.toLowerCase() !== trackStyle[s].title.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('artist'))
{
if (sArtist === false)
continue;
if (sArtist.toLowerCase() !== trackStyle[s].artist.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('attribute'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes(trackStyle[s].attribute.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('livelearn'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes('livelearn'))
continue;
}
if (!measureOnly)
document.getElementById('track').classList.add(s);
document.getElementById('measure').classList.add(s);
}
}
async function hideTrack()
{
let t = document.getElementById('track');
if (t.style.display !== 'inline-block')
{
t.style.opacity = '0';
t.style.display = 'inline-block';
}
else
{
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '0';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
t.style.opacity = '';
t.style.visibility = '';
t.innerHTML = '';
}
function measureTrack(s)
{
styleTrack(true);
let t = document.getElementById('measure');
t.style.display = 'inline-block';
t.innerHTML = s;
let w = t.getBoundingClientRect()['width'];
t.style.display = '';
t.innerHTML = '';
return w;
}
async function displayTrack()
{
let t = document.getElementById('track');
let l = document.getElementById('label');
if (sTitle === false)
{
lastW = 0;
lastD = 0;
await hideTable()
if (t.hasAttribute('style'))
t.removeAttribute('style');
t.innerHTML = '';
return;
}
let w = document.getElementById('trackBox').getBoundingClientRect()['width'];
let dTitle = titleDisp.replace('%TITLE%', sTitle);
let dArtist = '';
if (artistDisp !== false && sArtist !== false)
dArtist = artistDisp.replace('%ARTIST%', sArtist);
let dUser = '';
if (userDisp !== false && sUser !== false)
dUser = userDisp.replace('%USER%', sUser);
let track = trackDisp.replace('%DISP_TITLE%', dTitle).replace('%DISP_ARTIST%', dArtist).replace('%DISP_USER%', dUser);
let d = measureTrack(track);
let h = false;
let range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (label !== '')
{
if (d + (normPad * 2) > w - range)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (fitScroll)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (l.style.paddingRight !== '')
{
await hideTrack();
l.style.paddingRight = '';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
}
}
if (!h && w === lastW && d === lastD)
return;
lastW = w;
lastD = d;
if (!h)
await hideTrack();
styleTrack(false);
range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (d + (normPad * 2) > w - range)
{
d -= w;
d += fadePad * 2;
let u = Math.ceil(d * scrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
showFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '-' + Math.ceil(d) + 'px');
t.style.marginLeft = fadePad + 'px';
t.style.marginRight = fadePad + 'px';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (fitScroll)
{
let m = w - d;
m -= normPad;
let u = Math.ceil(m * fitScrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', Math.ceil(m) + 'px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (l.style.paddingRight !== '' && l.style.paddingRight !== '0px')
l.style.paddingRight = '';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '0px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = '';
await showTable();
await showTrack();
}
}
}
function showLabel()
{
let l = document.getElementById('label');
if (label === '')
{
l.style.paddingLeft = '0px';
l.style.paddingRight = '0px';
l.style.width = '1px';
l.innerHTML = '&ZeroWidthSpace;';
}
else
l.innerHTML = label;
}
</script>
</head>
<body>
<table id="trackTable">
<tr>
<td id="label" class="text"></td>
<td id="trackBox"><div id="track" class="track text"></div></td>
</tr>
</table>
<div id="measure" class="track text"></div>
<script>
async function init()
{
await loadUser();
window.setInterval(loadUser, interval * 1000);
showLabel();
window.setInterval(updateTrack, interval * 1000);
updateTrack();
}
init();
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<script>
var userID = 'your_channel'; /* twitch channel, lowercase */
var index = 2; /* queue index, starting from 1 for the next song and counting up */
var fadePad = 32; /* left and right margin */
var scrollRate = 20; /* lower is faster */
var interval = 2; /* update time in seconds */
var fadeTime = 0.5; /* fade speed in seconds */
var normPad = 8; /* label padding. must equal #label's left and right padding value */
var scrollPad = 6; /* padding between label and track name when scrolling */
var label = 'Coming Up:'; /* use '' for no label */
var fitScroll = false; /* set to true to scroll text that fits */
var fitScrollRate = 15; /* lower is faster */
var trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'; /* display of below variables */
var artistDisp = '%ARTIST% - '; /* artist name display (with text shown when an artist is provided, empty if no artist or live learn) */
var titleDisp = '%TITLE%'; /* title display */
var userDisp = ' for %USER%'; /* requester username display (with text shown when a song is by request, empty if no requester) */
var emptyOpenDisp = '' /* message to display when the queue does not have enough songs to reach this index and song requests are open */
var emptyClosedDisp = ''; /* message to display when the queue does not have enough songs to reach this index and song requests are closed */
/*
* Example Track Displays:
*
* trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'
* artistDisp = '%ARTIST% - '
* titleDisp = '%TITLE%'
* userDisp = 'for %USER'
* => Pink Floyd - Wish You Were Here for RealityRipple
*
* trackDisp = '%DISP_USER%%DISP_TITLE%%DISP_ARTIST%'
* artistDisp = ' by %ARTIST%'
* titleDisp = '%TITLE%'
* userDisp = '%USER%\'s Request: '
* => RealityRipple's Request: Wish You Were Here by Pink Floyd
*
* trackDisp = '%DISP_TITLE%'
* artistDisp = '';
* titleDisp = '%TITLE%';
* userDisp = '';
* => Wish You Were Here
*/
var trackStyle = {
'style1': {title: 'Stairway to Heaven'},
'style2': {artist: 'Led Zeppelin'},
'style3': {attribute: '70s Rock'},
'style4': {livelearn: true},
'style5': {user: 'RealityRipple'}
};
</script>
<style>
.text
{
font-size: 32px;
line-height: 36px; /* adjust as needed for the text-shadow */
color: #FFFFFF;
text-shadow: 2px 2px 3px #000000;
/* font-family: 'Comic Sans MS'; */
}
.style1
{
color: #FFFF00;
}
.style2
{
font-style: italic;
}
.style3
{
font-weight: bold;
background: linear-gradient(to top, #FF0000, #FFFF00);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none;
}
.style4
{
color: #00FFFF;
}
.style5
{
color: #008000;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Track Display for StreamerSongList</title>
<style>
:root
{
--pos: 0px;
}
body
{
text-align: left;
margin: 0;
}
table
{
border: 0;
visibility: hidden;
position: fixed;
opacity: 0;
}
#label
{
margin: 0;
background-color: transparent;
padding: 2px 8px 8px;
white-space: nowrap;
}
#trackBox
{
width: 100%;
overflow: hidden;
position: relative;
}
.track
{
margin: 0;
top: 0;
display: none;
position: absolute;
background-color: transparent;
padding: 2px 0;
white-space: nowrap;
animation-name: fullWidth;
animation-timing-function: ease-in-out;
animation-direction: alternate;
animation-iteration-count: infinite;
visibility: hidden;
opacity: 0;
}
@keyframes fullWidth
{
from
{
transform: translate(0, 0);
}
to
{
transform: translate(var(--pos), 0);
}
}
</style>
<script>
var sTitle = false;
var sArtist = false;
var sUser = false;
var iAttr = [];
var lastW = 0;
var lastD = 0;
var attrs = {};
var queueOpen = false;
var queueMode = false;
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
function loadUser()
{
let p = new Promise((resolve, reject) =>
{
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID + '?platform=twitch', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseAttributes(x.responseText);
parseRequests(x.responseText);
resolve(true);
}
x.send(null);
}
);
return p;
}
function parseAttributes(sJSON)
{
attrs = {};
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('attributes'))
return;
if (!Array.isArray(j.attributes))
return;
for (let i = 0; i < j.attributes.length; i++)
{
if (!j.attributes[i].hasOwnProperty('id'))
continue;
if (!j.attributes[i].hasOwnProperty('name'))
continue;
attrs[j.attributes[i].id] = j.attributes[i].name;
}
}
function parseRequests(sJSON)
{
queueOpen = false;
queueMode = false;
let j = JSON.parse(sJSON);
if (j.hasOwnProperty('requestsActive'))
queueOpen = j.requestsActive;
if (j.hasOwnProperty('canAnonymousRequest') && j.canAnonymousRequest)
{
queueMode = 'Anyone';
return;
}
if (j.hasOwnProperty('canUserRequest') && j.canUserRequest)
{
queueMode = 'Users';
return;
}
if (j.hasOwnProperty('canFollowerRequest') && j.canFollowerRequest)
{
queueMode = 'Followers';
return;
}
if (j.hasOwnProperty('canSubscriberRequest') && j.canFollowerRequest)
{
queueMode = 'Subscribers';
return;
}
if (j.hasOwnProperty('canSubscriberT2Request') && j.canFollowerRequest)
{
queueMode = 'Tier 2 Subscribers';
return;
}
if (j.hasOwnProperty('canSubscriberT3Request') && j.canFollowerRequest)
{
queueMode = 'Tier 3 Subscribers';
return;
}
queueMode = 'Moderators';
}
function updateTrack()
{
sTitle = false;
sArtist = false;
sUser = false;
iAttr = [];
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID +'/queue', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseTrack(x.responseText);
displayTrack();
}
x.send(null);
}
function parseTrack(sJSON)
{
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('list') || !Array.isArray(j.list) || j.list.length < index + 1)
{
sTitle = false;
if (queueOpen)
{
if (emptyOpenDisp !== null && emptyOpenDisp !== '')
sTitle = emptyOpenDisp.replace('%TYPE%', queueMode);
}
else
{
if (emptyClosedDisp !== null && emptyClosedDisp !== '')
sTitle = emptyClosedDisp;
}
sArtist = false;
sUser = false;
iAttr = [];
return;
}
if (j.list[index].hasOwnProperty('requests') && Array.isArray(j.list[index].requests) && j.list[index].requests.length > 0)
{
let sUsers = [];
for (let i = 0; i < j.list[index].requests.length; i++)
{
if (!j.list[index].requests[i].hasOwnProperty('name') || j.list[index].requests[i].name === '')
continue;
sUsers.push(j.list[index].requests[i].name);
}
if (sUsers.length === 0)
sUser = false;
else
sUser = sUsers.join(', ');
}
if (j.list[index].hasOwnProperty('nonlistSong') && j.list[index].nonlistSong !== null && j.list[index].nonlistSong !== '')
{
sTitle = j.list[index].nonlistSong;
sArtist = false;
iAttr = ['livelearn'];
return;
}
else if (j.list[index].hasOwnProperty('song') && j.list[index].song !== null)
{
if (j.list[index].song.hasOwnProperty('title') && j.list[index].song.title !== '')
sTitle = j.list[index].song.title;
else
sTitle = false;
if (j.list[index].song.hasOwnProperty('artist') && j.list[index].song.artist !== '')
sArtist = j.list[index].song.artist;
else
sArtist = false;
iAttr = [];
if (j.list[index].song.hasOwnProperty('attributeIds') && Array.isArray(j.list[index].song.attributeIds))
{
for (let n = 0; n < j.list[index].song.attributeIds.length; n++)
{
iAttr.push(attrs[j.list[index].song.attributeIds[n]].toLowerCase());
}
}
}
}
async function showTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility === 'visible')
return;
tbl.style.opacity = '0';
tbl.style.visibility = 'visible';
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '1';
}
async function hideTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility !== 'visible')
return;
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '0';
await sleep(fadeTime * 1000);
tbl.style.transitionProperty = '';
tbl.style.transitionDuration = '';
tbl.style.visibility = '';
tbl.style.opacity = '';
}
function showFade()
{
let tb = document.getElementById('trackBox');
if (!tb.hasAttribute('style'))
tb.setAttribute('style', '-webkit-mask-image: linear-gradient(to right, transparent 0, black ' + fadePad + 'px, black calc(100% - ' + fadePad + 'px), transparent 100%);');
}
function hideFade()
{
let tb = document.getElementById('trackBox');
if (tb.hasAttribute('style'))
tb.removeAttribute('style');
}
async function showTrack()
{
let t = document.getElementById('track');
if (t.style.visibility === 'visible')
return;
t.style.visibility = 'visible';
t.style.opacity = '0';
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '1';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
function styleTrack(measureOnly)
{
if (trackStyle === false)
return;
for (let s in trackStyle)
{
if (!measureOnly)
{
if (document.getElementById('track').classList.contains(s))
document.getElementById('track').classList.remove(s);
}
if (document.getElementById('measure').classList.contains(s))
document.getElementById('measure').classList.remove(s);
if (trackStyle[s].hasOwnProperty('user'))
{
if (sUser === false)
continue;
if (!sUser.toLowerCase().split(', ').includes(trackStyle[s].user.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('title'))
{
if (sTitle === false)
continue;
if (sTitle.toLowerCase() !== trackStyle[s].title.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('artist'))
{
if (sArtist === false)
continue;
if (sArtist.toLowerCase() !== trackStyle[s].artist.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('attribute'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes(trackStyle[s].attribute.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('livelearn'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes('livelearn'))
continue;
}
if (!measureOnly)
document.getElementById('track').classList.add(s);
document.getElementById('measure').classList.add(s);
}
}
async function hideTrack()
{
let t = document.getElementById('track');
if (t.style.display !== 'inline-block')
{
t.style.opacity = '0';
t.style.display = 'inline-block';
}
else
{
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '0';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
t.style.opacity = '';
t.style.visibility = '';
t.innerHTML = '';
}
function measureTrack(s)
{
styleTrack(true);
let t = document.getElementById('measure');
t.style.display = 'inline-block';
t.innerHTML = s;
let w = t.getBoundingClientRect()['width'];
t.style.display = '';
t.innerHTML = '';
return w;
}
async function displayTrack()
{
let t = document.getElementById('track');
let l = document.getElementById('label');
if (sTitle === false)
{
lastW = 0;
lastD = 0;
await hideTable()
if (t.hasAttribute('style'))
t.removeAttribute('style');
t.innerHTML = '';
return;
}
let w = document.getElementById('trackBox').getBoundingClientRect()['width'];
let dTitle = titleDisp.replace('%TITLE%', sTitle);
let dArtist = '';
if (artistDisp !== false && sArtist !== false)
dArtist = artistDisp.replace('%ARTIST%', sArtist);
let dUser = '';
if (userDisp !== false && sUser !== false)
dUser = userDisp.replace('%USER%', sUser);
let track = trackDisp.replace('%DISP_TITLE%', dTitle).replace('%DISP_ARTIST%', dArtist).replace('%DISP_USER%', dUser);
let d = measureTrack(track);
let h = false;
let range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (label !== '')
{
if (d + (normPad * 2) > w - range)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (fitScroll)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (l.style.paddingRight !== '')
{
await hideTrack();
l.style.paddingRight = '';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
}
}
if (!h && w === lastW && d === lastD)
return;
lastW = w;
lastD = d;
if (!h)
await hideTrack();
styleTrack(false);
range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (d + (normPad * 2) > w - range)
{
d -= w;
d += fadePad * 2;
let u = Math.ceil(d * scrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
showFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '-' + Math.ceil(d) + 'px');
t.style.marginLeft = fadePad + 'px';
t.style.marginRight = fadePad + 'px';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (fitScroll)
{
let m = w - d;
m -= normPad;
let u = Math.ceil(m * fitScrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', Math.ceil(m) + 'px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (l.style.paddingRight !== '' && l.style.paddingRight !== '0px')
l.style.paddingRight = '';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '0px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = '';
await showTable();
await showTrack();
}
}
}
function showLabel()
{
let l = document.getElementById('label');
if (label === '')
{
l.style.paddingLeft = '0px';
l.style.paddingRight = '0px';
l.style.width = '1px';
l.innerHTML = '&ZeroWidthSpace;';
}
else
l.innerHTML = label;
}
</script>
</head>
<body>
<table id="trackTable">
<tr>
<td id="label" class="text"></td>
<td id="trackBox"><div id="track" class="track text"></div></td>
</tr>
</table>
<div id="measure" class="track text"></div>
<script>
async function init()
{
await loadUser();
window.setInterval(loadUser, interval * 1000);
showLabel();
window.setInterval(updateTrack, interval * 1000);
updateTrack();
}
init();
</script>
</body>
</html>
<!doctype html>
<html>
<head>
<script>
var userID = 'your_channel'; /* twitch channel, lowercase */
var fadePad = 32; /* left and right margin */
var scrollRate = 20; /* lower is faster */
var interval = 2; /* update time in seconds */
var fadeTime = 0.5; /* fade speed in seconds */
var normPad = 8; /* label padding. must equal #label's left and right padding value */
var scrollPad = 6; /* padding between label and track name when scrolling */
var label = 'Now Playing:'; /* use '' for no label */
var fitScroll = false; /* set to true to scroll text that fits */
var fitScrollRate = 15; /* lower is faster */
var trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'; /* display of below variables */
var artistDisp = '%ARTIST% - '; /* artist name display (with text shown when an artist is provided, empty if no artist or live learn) */
var titleDisp = '%TITLE%'; /* title display */
var userDisp = ' for %USER%'; /* requester username display (with text shown when a song is by request, empty if no requester) */
/*
* Example Track Displays:
*
* trackDisp = '%DISP_ARTIST%%DISP_TITLE%%DISP_USER%'
* artistDisp = '%ARTIST% - '
* titleDisp = '%TITLE%'
* userDisp = 'for %USER'
* => Pink Floyd - Wish You Were Here for RealityRipple
*
* trackDisp = '%DISP_USER%%DISP_TITLE%%DISP_ARTIST%'
* artistDisp = ' by %ARTIST%'
* titleDisp = '%TITLE%'
* userDisp = '%USER%\'s Request: '
* => RealityRipple's Request: Wish You Were Here by Pink Floyd
*
* trackDisp = '%DISP_TITLE%'
* artistDisp = '';
* titleDisp = '%TITLE%';
* userDisp = '';
* => Wish You Were Here
*/
var trackStyle = {
'style1': {title: 'Stairway to Heaven'},
'style2': {artist: 'Led Zeppelin'},
'style3': {attribute: '70s Rock'},
'style4': {livelearn: true},
'style5': {user: 'RealityRipple'}
};
</script>
<style>
.text
{
font-size: 32px;
line-height: 36px; /* adjust as needed for the text-shadow */
color: #FFFFFF;
text-shadow: 2px 2px 3px #000000;
/* font-family: 'Comic Sans MS'; */
}
.style1
{
color: #FFFF00;
}
.style2
{
font-style: italic;
}
.style3
{
font-weight: bold;
background: linear-gradient(to top, #FF0000, #FFFF00);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none;
}
.style4
{
color: #00FFFF;
}
.style5
{
color: #008000;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Track Display for StreamerSongList</title>
<style>
:root
{
--pos: 0px;
}
body
{
text-align: left;
margin: 0;
}
table
{
border: 0;
visibility: hidden;
position: fixed;
opacity: 0;
}
#label
{
margin: 0;
background-color: transparent;
padding: 2px 8px 8px;
white-space: nowrap;
}
#trackBox
{
width: 100%;
overflow: hidden;
position: relative;
}
.track
{
margin: 0;
top: 0;
display: none;
position: absolute;
background-color: transparent;
padding: 2px 0;
white-space: nowrap;
animation-name: fullWidth;
animation-timing-function: ease-in-out;
animation-direction: alternate;
animation-iteration-count: infinite;
visibility: hidden;
opacity: 0;
}
@keyframes fullWidth
{
from
{
transform: translate(0, 0);
}
to
{
transform: translate(var(--pos), 0);
}
}
</style>
<script>
var sTitle = false;
var sArtist = false;
var sUser = false;
var iAttr = [];
var lastW = 0;
var lastD = 0;
var attrs = {};
function sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
function loadUser()
{
let p = new Promise((resolve, reject) =>
{
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID + '?platform=twitch', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseAttributes(x.responseText);
resolve(true);
}
x.send(null);
}
);
return p;
}
function parseAttributes(sJSON)
{
attrs = {};
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('attributes'))
return;
if (!Array.isArray(j.attributes))
return;
for (let i = 0; i < j.attributes.length; i++)
{
if (!j.attributes[i].hasOwnProperty('id'))
continue;
if (!j.attributes[i].hasOwnProperty('name'))
continue;
attrs[j.attributes[i].id] = j.attributes[i].name;
}
}
function updateTrack()
{
sTitle = false;
sArtist = false;
sUser = false;
iAttr = [];
let x = new XMLHttpRequest();
x.open('GET', 'https://api.streamersonglist.com/v1/streamers/' + userID +'/queue', true);
x.onreadystatechange = function()
{
if(x.readyState !== 4)
return;
if (x.status !== 200 && x.status !== 0)
return;
if (x.responseText === '')
return;
parseTrack(x.responseText);
displayTrack();
}
x.send(null);
}
function parseTrack(sJSON)
{
let j = JSON.parse(sJSON);
if (!j.hasOwnProperty('list') || !Array.isArray(j.list) || j.list.length < 1)
{
sTitle = false;
sArtist = false;
sUser = false;
iAttr = [];
return;
}
if (j.list[0].hasOwnProperty('requests') && Array.isArray(j.list[0].requests) && j.list[0].requests.length > 0)
{
let sUsers = [];
for (let i = 0; i < j.list[0].requests.length; i++)
{
if (!j.list[0].requests[i].hasOwnProperty('name') || j.list[0].requests[i].name === '')
continue;
sUsers.push(j.list[0].requests[i].name);
}
if (sUsers.length === 0)
sUser = false;
else
sUser = sUsers.join(', ');
}
if (j.list[0].hasOwnProperty('nonlistSong') && j.list[0].nonlistSong !== null && j.list[0].nonlistSong !== '')
{
sTitle = j.list[0].nonlistSong;
sArtist = false;
iAttr = ['livelearn'];
return;
}
else if (j.list[0].hasOwnProperty('song') && j.list[0].song !== null)
{
if (j.list[0].song.hasOwnProperty('title') && j.list[0].song.title !== '')
sTitle = j.list[0].song.title;
else
sTitle = false;
if (j.list[0].song.hasOwnProperty('artist') && j.list[0].song.artist !== '')
sArtist = j.list[0].song.artist;
else
sArtist = false;
iAttr = [];
if (j.list[0].song.hasOwnProperty('attributeIds') && Array.isArray(j.list[0].song.attributeIds))
{
for (let n = 0; n < j.list[0].song.attributeIds.length; n++)
{
iAttr.push(attrs[j.list[0].song.attributeIds[n]].toLowerCase());
}
}
}
}
async function showTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility === 'visible')
return;
tbl.style.opacity = '0';
tbl.style.visibility = 'visible';
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '1';
}
async function hideTable()
{
let tbl = document.getElementById('trackTable');
if (tbl.style.visibility !== 'visible')
return;
tbl.style.transitionProperty = 'opacity';
tbl.style.transitionDuration = fadeTime + 's';
tbl.style.opacity = '0';
await sleep(fadeTime * 1000);
tbl.style.transitionProperty = '';
tbl.style.transitionDuration = '';
tbl.style.visibility = '';
tbl.style.opacity = '';
}
function showFade()
{
let tb = document.getElementById('trackBox');
if (!tb.hasAttribute('style'))
tb.setAttribute('style', '-webkit-mask-image: linear-gradient(to right, transparent 0, black ' + fadePad + 'px, black calc(100% - ' + fadePad + 'px), transparent 100%);');
}
function hideFade()
{
let tb = document.getElementById('trackBox');
if (tb.hasAttribute('style'))
tb.removeAttribute('style');
}
async function showTrack()
{
let t = document.getElementById('track');
if (t.style.visibility === 'visible')
return;
t.style.visibility = 'visible';
t.style.opacity = '0';
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '1';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
function styleTrack(measureOnly)
{
if (trackStyle === false)
return;
for (let s in trackStyle)
{
if (!measureOnly)
{
if (document.getElementById('track').classList.contains(s))
document.getElementById('track').classList.remove(s);
}
if (document.getElementById('measure').classList.contains(s))
document.getElementById('measure').classList.remove(s);
if (trackStyle[s].hasOwnProperty('user'))
{
if (sUser === false)
continue;
if (!sUser.toLowerCase().split(', ').includes(trackStyle[s].user.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('title'))
{
if (sTitle === false)
continue;
if (sTitle.toLowerCase() !== trackStyle[s].title.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('artist'))
{
if (sArtist === false)
continue;
if (sArtist.toLowerCase() !== trackStyle[s].artist.toLowerCase())
continue;
}
if (trackStyle[s].hasOwnProperty('attribute'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes(trackStyle[s].attribute.toLowerCase()))
continue;
}
if (trackStyle[s].hasOwnProperty('livelearn'))
{
if (iAttr.length === 0)
continue;
if (!iAttr.includes('livelearn'))
continue;
}
if (!measureOnly)
document.getElementById('track').classList.add(s);
document.getElementById('measure').classList.add(s);
}
}
async function hideTrack()
{
let t = document.getElementById('track');
if (t.style.display !== 'inline-block')
{
t.style.opacity = '0';
t.style.display = 'inline-block';
}
else
{
t.style.transitionProperty = 'opacity';
t.style.transitionDuration = fadeTime + 's';
t.style.opacity = '0';
await sleep(fadeTime * 1000);
t.style.transitionProperty = '';
t.style.transitionDuration = '';
}
t.style.opacity = '';
t.style.visibility = '';
t.innerHTML = '';
}
function measureTrack(s)
{
styleTrack(true);
let t = document.getElementById('measure');
t.style.display = 'inline-block';
t.innerHTML = s;
let w = t.getBoundingClientRect()['width'];
t.style.display = '';
t.innerHTML = '';
return w;
}
async function displayTrack()
{
let t = document.getElementById('track');
let l = document.getElementById('label');
if (sTitle === false)
{
lastW = 0;
lastD = 0;
await hideTable()
if (t.hasAttribute('style'))
t.removeAttribute('style');
t.innerHTML = '';
return;
}
let w = document.getElementById('trackBox').getBoundingClientRect()['width'];
let dTitle = titleDisp.replace('%TITLE%', sTitle);
let dArtist = '';
if (artistDisp !== false && sArtist !== false)
dArtist = artistDisp.replace('%ARTIST%', sArtist);
let dUser = '';
if (userDisp !== false && sUser !== false)
dUser = userDisp.replace('%USER%', sUser);
let track = trackDisp.replace('%DISP_TITLE%', dTitle).replace('%DISP_ARTIST%', dArtist).replace('%DISP_USER%', dUser);
let d = measureTrack(track);
let h = false;
let range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (label !== '')
{
if (d + (normPad * 2) > w - range)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (fitScroll)
{
if (l.style.paddingRight !== scrollPad + 'px')
{
await hideTrack();
l.style.paddingRight = scrollPad + 'px';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
else
{
if (l.style.paddingRight !== '')
{
await hideTrack();
l.style.paddingRight = '';
w = document.getElementById('trackBox').getBoundingClientRect()['width'];
h = true;
}
}
}
}
if (!h && w === lastW && d === lastD)
return;
lastW = w;
lastD = d;
if (!h)
await hideTrack();
styleTrack(false);
range = 0;
if (l.style.paddingRight === scrollPad + 'px')
range = Math.abs((normPad * 2) - (normPad + scrollPad));
if (d + (normPad * 2) > w - range)
{
d -= w;
d += fadePad * 2;
let u = Math.ceil(d * scrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
showFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '-' + Math.ceil(d) + 'px');
t.style.marginLeft = fadePad + 'px';
t.style.marginRight = fadePad + 'px';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (fitScroll)
{
let m = w - d;
m -= normPad;
let u = Math.ceil(m * fitScrollRate);
if (u < 2000)
u = 2000;
if (l.style.paddingRight !== scrollPad + 'px' && l.style.paddingRight !== '0px')
l.style.paddingRight = scrollPad + 'px';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', Math.ceil(m) + 'px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = u + 'ms';
await showTable();
await showTrack();
}
else
{
if (l.style.paddingRight !== '' && l.style.paddingRight !== '0px')
l.style.paddingRight = '';
hideFade();
t.innerHTML = track;
document.documentElement.style.setProperty('--pos', '0px');
t.style.marginLeft = '';
t.style.marginRight = '';
t.style.animationDuration = '';
await showTable();
await showTrack();
}
}
}
function showLabel()
{
let l = document.getElementById('label');
if (label === '')
{
l.style.paddingLeft = '0px';
l.style.paddingRight = '0px';
l.style.width = '1px';
l.innerHTML = '&ZeroWidthSpace;';
}
else
l.innerHTML = label;
}
</script>
</head>
<body>
<table id="trackTable">
<tr>
<td id="label" class="text"></td>
<td id="trackBox"><div id="track" class="track text"></div></td>
</tr>
</table>
<div id="measure" class="track text"></div>
<script>
async function init()
{
await loadUser();
window.setInterval(loadUser, interval * 1000);
showLabel();
window.setInterval(updateTrack, interval * 1000);
updateTrack();
}
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment