Add AppleMusic Module
This commit is contained in:
261
modules/apple-music/apple-music.js
Normal file
261
modules/apple-music/apple-music.js
Normal file
@@ -0,0 +1,261 @@
|
||||
// Apple Music Overlay Module
|
||||
// This module connects to a local Cider instance to display Apple Music playback information.
|
||||
// Initial sources were taken from https://github.com/nuttylmao/nutty.gg
|
||||
|
||||
;(function() {
|
||||
let visibilityDefault = 5;
|
||||
let visibilityDuration = visibilityDefault;
|
||||
let animationSpeed = 0.5;
|
||||
|
||||
const moduleUrl = window.getModuleUrl();
|
||||
loadCSSModule('overlay-applemusic-css', moduleUrl + '/css/apple-music.css');
|
||||
loadModuleResources([
|
||||
{ type:'css', url:'https://cdn.jsdelivr.net/npm/@xz/fonts@1/serve/metropolis.min.css', id:'xz-fonts' },
|
||||
{ type:'js', url:'https://cdn.socket.io/4.7.5/socket.io.min.js', id:'socket-io', crossorigin:'anonymous' },
|
||||
{ type:'js', url:'https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js', id:'tweenmax' }
|
||||
]).then(() => {
|
||||
initModule();
|
||||
let outer = document.getElementById('musicWidgetContainer');
|
||||
let maxWidth = outer.clientWidth+50;
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
connectws();
|
||||
|
||||
function initModule() {
|
||||
const container = document.getElementById('mainContainer') || document.body;
|
||||
|
||||
// Crée le conteneur principal
|
||||
if (!document.getElementById('musicWidgetContainer')) {
|
||||
const widget = document.createElement('div');
|
||||
widget.id = 'musicWidgetContainer';
|
||||
|
||||
// Album art box
|
||||
const artBox = document.createElement('div');
|
||||
artBox.id = 'albumArtBox';
|
||||
const albumArt = document.createElement('img');
|
||||
albumArt.id = 'albumArt';
|
||||
albumArt.src = ''; // placeholder
|
||||
const albumArtBack = document.createElement('img');
|
||||
albumArtBack.id = 'albumArtBack';
|
||||
albumArtBack.src = ''; // placeholder
|
||||
artBox.append(albumArt, albumArtBack);
|
||||
|
||||
// Song info box
|
||||
const infoBox = document.createElement('div');
|
||||
infoBox.id = 'songInfoBox';
|
||||
|
||||
const songInfo = document.createElement('div');
|
||||
songInfo.id = 'songInfo';
|
||||
const innerBox = document.createElement('div');
|
||||
innerBox.id = 'IAmRunningOutOfNamesForTheseBoxes';
|
||||
|
||||
const songLabel = document.createElement('div');
|
||||
songLabel.id = 'songLabel';
|
||||
songLabel.innerText = '-/-';
|
||||
const artistLabel = document.createElement('div');
|
||||
artistLabel.id = 'artistLabel';
|
||||
artistLabel.innerText = '-/-';
|
||||
const albumLabel = document.createElement('div');
|
||||
albumLabel.id = 'albumLabel';
|
||||
albumLabel.innerText = '-/-';
|
||||
|
||||
const times = document.createElement('div');
|
||||
times.id = 'times';
|
||||
const progressTime = document.createElement('div');
|
||||
progressTime.id = 'progressTime';
|
||||
progressTime.innerText = '0:00';
|
||||
const duration = document.createElement('div');
|
||||
duration.id = 'duration';
|
||||
duration.innerText = '0:00';
|
||||
times.append(progressTime, duration);
|
||||
|
||||
const progressBg = document.createElement('div');
|
||||
progressBg.id = 'progressBg';
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.id = 'progressBar';
|
||||
progressBg.appendChild(progressBar);
|
||||
|
||||
innerBox.append(songLabel, artistLabel, albumLabel, times, progressBg);
|
||||
songInfo.appendChild(innerBox);
|
||||
|
||||
const backgroundArt = document.createElement('div');
|
||||
backgroundArt.id = 'backgroundArt';
|
||||
const backgroundImage = document.createElement('img');
|
||||
backgroundImage.id = 'backgroundImage';
|
||||
backgroundImage.src = '';
|
||||
const backgroundImageBack = document.createElement('img');
|
||||
backgroundImageBack.id = 'backgroundImageBack';
|
||||
backgroundImageBack.src = '';
|
||||
backgroundArt.append(backgroundImage, backgroundImageBack);
|
||||
|
||||
infoBox.append(songInfo, backgroundArt);
|
||||
|
||||
widget.append(artBox, infoBox);
|
||||
container.appendChild(widget);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.SBdispatcher) {
|
||||
SBdispatcher.on('musicPlaying', data => {
|
||||
visibilityDuration = data.param1 || visibilityDefault;
|
||||
showSongInfo();
|
||||
});
|
||||
}
|
||||
|
||||
function connectws() {
|
||||
if ("WebSocket" in window) {
|
||||
const CiderApp = io("http://localhost:10767/", {
|
||||
transports: ['websocket']
|
||||
});
|
||||
|
||||
CiderApp.on("disconnect", (event) => {
|
||||
SetConnectionStatus(false);
|
||||
setTimeout(connectws, 5000);
|
||||
});
|
||||
|
||||
CiderApp.on("connect", (event) => {
|
||||
SetConnectionStatus(true);
|
||||
});
|
||||
|
||||
// Set up websocket artwork/information handling
|
||||
CiderApp.on("API:Playback", ({ data, type }) => {
|
||||
switch (type) {
|
||||
// Song changes
|
||||
case ("playbackStatus.nowPlayingItemDidChange"):
|
||||
UpdateSongInfo(data);
|
||||
break;
|
||||
|
||||
// Progress bar moves
|
||||
case ("playbackStatus.playbackTimeDidChange"):
|
||||
UpdateProgressBar(data);
|
||||
break;
|
||||
|
||||
// Pause/unpause
|
||||
case ("playbackStatus.playbackStateDidChange"):
|
||||
UpdatePlaybackState(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showSongInfo() {
|
||||
setTimeout(() => {
|
||||
SetVisibility(true);
|
||||
}, animationSpeed * 500);
|
||||
|
||||
if (visibilityDuration > 0) {
|
||||
setTimeout(() => {
|
||||
SetVisibility(false);
|
||||
}, visibilityDuration * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function UpdateSongInfo(data) {
|
||||
// Set the user's info
|
||||
let albumArtUrl = data.artwork.url;
|
||||
albumArtUrl = albumArtUrl.replace("{w}", data.artwork.width);
|
||||
albumArtUrl = albumArtUrl.replace("{h}", data.artwork.height);
|
||||
|
||||
UpdateAlbumArt(document.getElementById("albumArt"), albumArtUrl);
|
||||
UpdateAlbumArt(document.getElementById("backgroundImage"), albumArtUrl);
|
||||
|
||||
setTimeout(() => {
|
||||
UpdateTextLabel(document.getElementById("songLabel"), data.name);
|
||||
UpdateTextLabel(document.getElementById("artistLabel"), data.artistName);
|
||||
}, animationSpeed * 500);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById("albumArtBack").src = albumArtUrl;
|
||||
document.getElementById("backgroundImageBack").src = albumArtUrl;
|
||||
}, 2 * animationSpeed * 500);
|
||||
|
||||
showSongInfo()
|
||||
|
||||
}
|
||||
|
||||
function UpdateTextLabel(div, text) {
|
||||
if (div.innerHTML != text) {
|
||||
div.setAttribute("class", "text-fade");
|
||||
setTimeout(() => {
|
||||
div.innerHTML = text;
|
||||
div.setAttribute("class", ".text-show");
|
||||
}, animationSpeed * 250);
|
||||
}
|
||||
}
|
||||
|
||||
function UpdateAlbumArt(div, imgsrc) {
|
||||
if (div.src != imgsrc) {
|
||||
div.setAttribute("class", "text-fade");
|
||||
setTimeout(() => {
|
||||
div.src = imgsrc;
|
||||
div.setAttribute("class", "text-show");
|
||||
}, animationSpeed * 500);
|
||||
}
|
||||
}
|
||||
|
||||
function UpdateProgressBar(data) {
|
||||
const progress = ((data.currentPlaybackTime / data.currentPlaybackDuration) * 100);
|
||||
const progressTime = ConvertSecondsToMinutesSoThatItLooksBetterOnTheOverlay(data.currentPlaybackTime);
|
||||
const duration = ConvertSecondsToMinutesSoThatItLooksBetterOnTheOverlay(data.currentPlaybackTimeRemaining);
|
||||
document.getElementById("progressBar").style.width = `${progress}%`;
|
||||
document.getElementById("progressTime").innerHTML = progressTime;
|
||||
document.getElementById("duration").innerHTML = `-${duration}`;
|
||||
}
|
||||
|
||||
function UpdatePlaybackState(data) {
|
||||
console.log(data);
|
||||
switch (data.state) {
|
||||
case ("paused"):
|
||||
case ("stopped"):
|
||||
SetVisibility(false);
|
||||
break;
|
||||
case ("playing"):
|
||||
UpdateSongInfo(data.attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ConvertSecondsToMinutesSoThatItLooksBetterOnTheOverlay(time) {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.trunc(time - minutes * 60);
|
||||
|
||||
return `${minutes}:${('0' + seconds).slice(-2)}`;
|
||||
}
|
||||
|
||||
function SetVisibility(isVisible) {
|
||||
widgetVisibility = isVisible;
|
||||
|
||||
const musicWidgetContainer = document.getElementById("musicWidgetContainer");
|
||||
|
||||
if (isVisible) {
|
||||
var tl = new TimelineMax();
|
||||
tl
|
||||
.to(musicWidgetContainer, animationSpeed, { bottom: "50%", ease: Power1.easeInOut }, 'label')
|
||||
.to(musicWidgetContainer, animationSpeed, { opacity: 1, ease: Power1.easeInOut }, 'label')
|
||||
}
|
||||
else {
|
||||
var tl = new TimelineMax();
|
||||
tl
|
||||
.to(musicWidgetContainer, animationSpeed, { bottom: "45%", ease: Power1.easeInOut }, 'label')
|
||||
.to(musicWidgetContainer, animationSpeed, { opacity: 0, ease: Power1.easeInOut }, 'label')
|
||||
}
|
||||
}
|
||||
|
||||
function SetConnectionStatus(connected) {
|
||||
if (connected) {
|
||||
console.log("Connected to Cider!");
|
||||
}
|
||||
else {
|
||||
console.log("Not connected to Cider...");
|
||||
}
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const scale = window.innerWidth / maxWidth;
|
||||
outer.style.transform = 'translate(0%, 0%) scale(' + scale + ')';
|
||||
}
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
})();
|
||||
139
modules/apple-music/css/apple-music.css
Normal file
139
modules/apple-music/css/apple-music.css
Normal file
@@ -0,0 +1,139 @@
|
||||
#musicWidgetContainer {
|
||||
--corner-radius: 10px;
|
||||
--album-art-size: 100px;
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
height: 140px;
|
||||
top: 20px;
|
||||
right: 0px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
height: var(--album-art-size);
|
||||
margin: 20px;
|
||||
filter: drop-shadow(0px 0px 4px #000000);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
bottom: 45%;
|
||||
opacity: 0;
|
||||
}
|
||||
#musicWidgetContainer #albumArtBox {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
border-radius: var(--corner-radius);
|
||||
overflow: hidden;
|
||||
margin: 0px 8px 0px 0px;
|
||||
}
|
||||
#musicWidgetContainer #albumArt {
|
||||
position: absolute;
|
||||
width: var(--album-art-size);
|
||||
}
|
||||
#musicWidgetContainer #albumArtBack {
|
||||
width: var(--album-art-size);
|
||||
}
|
||||
#musicWidgetContainer #songInfoBox {
|
||||
position: relative;
|
||||
color: white;
|
||||
width: calc(100% - 125px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 1 auto;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
text-shadow: 2px 2px 2px black;
|
||||
overflow: hidden;
|
||||
z-index: 4;
|
||||
}
|
||||
#musicWidgetContainer #songInfo {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
border-radius: var(--corner-radius);
|
||||
padding: 0px 20px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#musicWidgetContainer #IAmRunningOutOfNamesForTheseBoxes {
|
||||
width: calc(100% - 40px);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
#musicWidgetContainer #backgroundArt {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--corner-radius);
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
opacity: 0.9;
|
||||
}
|
||||
#musicWidgetContainer #backgroundImage {
|
||||
filter: blur(20px);
|
||||
position: absolute;
|
||||
width: 140%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
#musicWidgetContainer #backgroundImageBack {
|
||||
filter: blur(20px);
|
||||
position: absolute;
|
||||
width: 140%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: -1;
|
||||
}
|
||||
#musicWidgetContainer #songLabel {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#musicWidgetContainer #artistLabel {
|
||||
font-size: 16px;
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#musicWidgetContainer #albumLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#musicWidgetContainer #progressBg {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
background-color: #1F1F1F;
|
||||
}
|
||||
#musicWidgetContainer #progressBar {
|
||||
border-radius: 5px;
|
||||
height: 5px;
|
||||
width: 20%;
|
||||
background-color: #ffffff;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
#musicWidgetContainer #times {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
line-height: 3.5;
|
||||
}
|
||||
#musicWidgetContainer #progressTime {
|
||||
position: absolute;
|
||||
}
|
||||
#musicWidgetContainer #duration {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
#musicWidgetContainer .text-show {
|
||||
opacity: 1;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
#musicWidgetContainer .text-fade {
|
||||
opacity: 0;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
163
modules/apple-music/css/apple-music.less
Normal file
163
modules/apple-music/css/apple-music.less
Normal file
@@ -0,0 +1,163 @@
|
||||
// out: apple-music.css, sourcemap: false, compress: false
|
||||
|
||||
#musicWidgetContainer {
|
||||
--corner-radius: 10px;
|
||||
--album-art-size: 100px;
|
||||
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
height: 140px;
|
||||
top: 20px;
|
||||
right: 0px;
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
height: var(--album-art-size);
|
||||
margin: 20px;
|
||||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 1));
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
bottom: 45%;
|
||||
opacity: 0;
|
||||
|
||||
#albumArtBox {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
border-radius: var(--corner-radius);
|
||||
overflow: hidden;
|
||||
margin: 0px 8px 0px 0px;
|
||||
}
|
||||
|
||||
#albumArt {
|
||||
position: absolute;
|
||||
width: var(--album-art-size);
|
||||
}
|
||||
|
||||
#albumArtBack {
|
||||
width: var(--album-art-size);
|
||||
}
|
||||
|
||||
#songInfoBox {
|
||||
position: relative;
|
||||
color: white;
|
||||
width: calc(100% - 125px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 1 auto;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
text-shadow: 2px 2px 2px black;
|
||||
overflow: hidden;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
#songInfo {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
border-radius: var(--corner-radius);
|
||||
padding: 0px 20px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#IAmRunningOutOfNamesForTheseBoxes {
|
||||
position: absolute;
|
||||
width: calc(100% - 40px);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#backgroundArt {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--corner-radius);
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#backgroundImage {
|
||||
filter: blur(20px);
|
||||
position: absolute;
|
||||
width: 140%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
||||
#backgroundImageBack {
|
||||
filter: blur(20px);
|
||||
position: absolute;
|
||||
width: 140%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#songLabel {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#artistLabel {
|
||||
font-size: 16px;
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#albumLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#progressBg {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
background-color: #1F1F1F;
|
||||
}
|
||||
|
||||
#progressBar {
|
||||
border-radius: 5px;
|
||||
height: 5px;
|
||||
width: 20%;
|
||||
background-color: #ffffff;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
#times {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
line-height: 3.5;
|
||||
}
|
||||
|
||||
#progressTime {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#duration {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-show {
|
||||
opacity: 1;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.text-fade {
|
||||
opacity: 0;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,34 @@ function getModuleUrl() {
|
||||
return url.substring(0, url.lastIndexOf('/'));
|
||||
}
|
||||
|
||||
function loadModuleResources(resources) {
|
||||
const promises = resources.map(res => {
|
||||
const key = res.id || res.url;
|
||||
if (document.getElementById(key)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (res.type === 'css') {
|
||||
loadCSSModule(key, res.url);
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (res.type === 'js') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.id = key;
|
||||
script.src = res.url;
|
||||
if (res.integrity) script.integrity = res.integrity;
|
||||
if (res.crossorigin) script.crossOrigin = res.crossorigin;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(`Échec chargement script ${res.url}`));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
return Promise.all(promises).then(() => {});
|
||||
}
|
||||
|
||||
|
||||
function getBooleanParam(paramName, defaultValue) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paramValue = urlParams.get(paramName);
|
||||
@@ -245,4 +273,4 @@ function positionDiv(container) {
|
||||
|
||||
window.loadCSSModule = loadCSSModule;
|
||||
window.getModuleUrl = getModuleUrl;
|
||||
window.getBooleanParam = getBooleanParam;
|
||||
window.loadModuleResources = loadModuleResources;
|
||||
|
||||
@@ -17,4 +17,5 @@
|
||||
<script type="text/javascript" src="modules/alert-banner/alert-banner.js"></script>
|
||||
<script type="text/javascript" src="modules/big-emote/big-emote.js"></script>
|
||||
<script type="text/javascript" src="modules/alert-video/alert-video.js"></script>
|
||||
<script type="text/javascript" src="modules/apple-music/apple-music.js"></script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user