Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canva update #74

Merged
merged 21 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion MMM-OnSpotify.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ Module.register("MMM-OnSpotify", {
alwaysUseDefaultDeviceIcon: false,
showVerticalPipe: true,

// Show Canvas
experimentalCanvas: false,
// "contain" - Place the canvas in the frame leaving vertical stripes
// "scale" - Scale the container to fit the canvas
// "cover" - Fill the container to fit the canvas
experimentalCanvasEffect: "cover",
// Add a mini album cover
experimentalCanvasAlbumOverlay: true,
experimentalCanvasSPDCookie: "",

// In special use cases where a frontend needs to take over other you can disabl
// the id matching for the frontend, so "multiple" frontends can talk to the module even if not supported
matchBackendUUID: false,
Expand Down Expand Up @@ -200,6 +210,7 @@ Module.register("MMM-OnSpotify", {
this.userData = null;
this.playerData = null;
this.affinityData = null;
this.canvasData = null;
// this.queueData = null;
// this.recentData = null;

Expand Down Expand Up @@ -443,6 +454,10 @@ Module.register("MMM-OnSpotify", {
this.sendNotification("SERVERSIDE_RESTART");
this.sendCredentialsBackend();
break;
case "UPDATE_CANVAS":
this.canvasData = payload;
this.smartUpdate("CANVAS_DATA");
break
}
},
notificationReceived: function (notification, payload) {
Expand Down Expand Up @@ -549,7 +564,8 @@ Module.register("MMM-OnSpotify", {

smartUpdate: function (type) {
// Request data to display when the player is empty
// Update only if there is no data or the player is changing state
// Update only if there is no data or the player is changing state

this.requestUserData =
this.displayUser &&
this.isConnectedToSpotify &&
Expand Down Expand Up @@ -655,6 +671,8 @@ Module.register("MMM-OnSpotify", {
)
return this.builder.updatePlayerData(this.playerData);
}

if (type === "CANVAS_DATA") this.builder.updateCanvas(this.canvasData);
if (type === "USER_DATA") this.builder.updateUserData(this.userData);
if (type === "AFFINITY_DATA")
this.builder.updateAffinityData(this.affinityData);
Expand All @@ -668,12 +686,14 @@ Module.register("MMM-OnSpotify", {
userAffinityUseTracks: this.config.userAffinityUseTracks,
deviceFilter: this.config.deviceFilter,
deviceFilterExclude: this.config.deviceFilterExclude,
useCanvas: this.config.experimentalCanvas,
},
credentials: {
clientId: this.config.clientID,
clientSecret: this.config.clientSecret,
accessToken: this.config.accessToken,
refreshToken: this.config.refreshToken,
experimentalCanvasSPDCookie: this.config.experimentalCanvasSPDCookie,
},
language: this.config.language,
backendExpectId: this.backendExpectId,
Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ The module includes an Authentication Service that guide you through the configu
> [!NOTE]
> All the data stays in your mirror. If you have multiple mirrors, the Authentication Service guides you on creating a new Spotify App per mirror.


https://github.com/Fabrizz/MMM-OnSpotify/assets/65259076/5d78672e-8feb-45de-92f4-ed44f0771432

https://github.com/Fabrizz/MMM-OnSpotify/assets/65259076/f7c0928f-3806-48ba-a813-87962dd9ea8b

> This module shows what is on your Spotify Connect. if you want to use Spotify in your mirror you should look at [Raspotify](https://github.com/dtcooper/raspotify)

# Installation
### Step 1: Clone the module and install dependencies
Expand Down Expand Up @@ -41,7 +43,7 @@ Once you finish, you are all set with the basic configuration. Scroll down to se

# Module Configuration
#### The configuration section is divided in groups, scroll down or click what to see below:
### [[**NEW**] Theming 3rd Party Modules](#theming-3rd-party-modules) | [General](#general-options) | [Polling Intervals](#polling-intervals) | [Theming](#theming) | [**Lyrics**](#mmm-livelyrics) | [**Homekit**](#mmm-homekit)
### [[**NEW**] CANVAS](#canvas) | [Theming other modules](#theming-3rd-party-modules) | [General](#general-options) | [Polling Intervals](#polling-intervals) | [Theming](#theming) | [**Homekit**](#mmm-homekit) | [**Lyrics**](#mmm-livelyrics)

**Extended full configuration object:**
```js
Expand Down Expand Up @@ -85,6 +87,11 @@ Once you finish, you are all set with the basic configuration. Scroll down to se
spotifyCodeExperimentalShow: true,
spotifyCodeExperimentalUseColor: true,
spotifyCodeExperimentalSeparateItem: true,
// Canvas
experimentalCanvas: false,
experimentalCanvasEffect: 'cover',
experimentalCanvasAlbumOverlay: false,
experimentalCanvasSPDCookie: "",
// Theming General
roundMediaCorners: true,
roundProgressBar: true,
Expand Down Expand Up @@ -170,6 +177,16 @@ experimentalCSSOverridesForMM2: [
| spotifyCodeExperimentalUseColor <br> `true` | As shown on the image above, color the Spotify Code bar using cover art colors. |
| spotifyCodeExperimentalSeparateItem <br> `true` | Separates or joins the Spotify Code Bar to the cover art. Also respects `roundMediaCorners` and `spotifyCodeExperimentalUseColor`. <br /><br /><img alt="Spotify code bar separation" src=".github/content/readme/banner-codeseparation.png" aling="left" height="100"> |

#### Canvas
> [!CAUTION]
> Spotify returns server error 500 after * amount of anonymous usage of the API. You could get rate limited (or not, some people just use it anonymously!). For that you can add a `experimentalCanvasSPDCookie`. This is more advanced as it requieres you to get the cookie from a Spotify Web session. Not recommended for all users.
| Key | Description |
| :-- | :-- |
| experimentalCanvas <br> `false` | Shows the Spotify Canvas if available. This is an experimental feature, as this API is not documented and private. |
| canvasEffect <br> `cover` | Control how is the canvas is going to be displayed. Options are: <br />- `cover`: The Canvas is clipped to have the same height as the album cover. Recommended for low-power devices and if the module is not in a `bottom_*` position <br />- `scale`: Scale up/down the module to fit the entire Canvas without clipping it. <br /> |
| experimentalCanvasAlbumOverlay <br> `true` | Show the cover art inside the Spotify Canvas. |
| experimentalCanvasSPDCookie <br> `""` | Adds the SPD cookie from a web Spotify session to stop Spotify from returning a 500 error. Spotify could decide also to just send a 500 error depending on the user agents and other factors, this just affects the module. Still, this feature is optional and this API is NOT public. |

#### General Theming options
| Key | Description |
| :-- | :-- |
Expand All @@ -195,7 +212,7 @@ View more on the [**MMM-LiveLyrics** repository](https://github.com/Fabrizz/MMM-
## MMM-HomeKit
Control your mirror (and other modules) using Apple Homekit protocol! (Also compatible with HomeAssistant or other automation systems with simulated HK controllers)

#### >>> [MMM-HomeKit repository](https://github.com/Fabrizz/MMM-MMM-HomeKit) <<<
#### >>> [MMM-HomeKit repository](https://github.com/Fabrizz/MMM-HomeKit) <<<
- Control your screen on/off, brightness
- Set the mirror accent color
- Turn on/off on-screen lyrics
Expand All @@ -204,7 +221,7 @@ Control your mirror (and other modules) using Apple Homekit protocol! (Also comp

<img alt="MMM-Homekit" src=".github/content/readme/banner-homekit.png" width="70%">

> To learn how to use OnSpotify and HomeKit provided accent colors together, read the guide in the [MMM-HomeKit repository](https://github.com/Fabrizz/MMM-MMM-HomeKit).
> To learn how to use OnSpotify and HomeKit provided accent colors together, read the guide in the [MMM-HomeKit repository](https://github.com/Fabrizz/MMM-HomeKit).

## Notification API
| key | Description |
Expand Down
4 changes: 2 additions & 2 deletions auth/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ <h4 class="card-header py-3">Step 3: Configure your mirror</h4>
clientID: <span style="color: rgb(242, 210, 152);">"{{clientID}}"</span>,
clientSecret: <span style="color: rgb(242, 210, 152);">"{{clientSecret}}"</span>,
accessToken: <span style="color: rgb(242, 210, 152);">"{{accessToken}}"</span>,
refreshToken: <span style="color: rgb(242, 210, 152);">"{{refreshToken}}"</span>
<span style="color: rgba(255, 255, 255, 0.48);">/* Add here your theming and behaviour configurations */</span>
refreshToken: <span style="color: rgb(242, 210, 152);">"{{refreshToken}}"</span>,
<span style="color: rgba(255, 255, 255, 0.48);">/* Add here your configurations */</span>
}
},</pre>

Expand Down
70 changes: 58 additions & 12 deletions css/included.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,17 @@
--ONSP-INTERNAL-LOWPOWER-COVER: none;
--ONSP-INTERNAL-LOWPOWER-TRANSITIONS: none; /* background-color */

--ONSP-INTERNAL-COVER-FADE-TIME: 2500ms;
--ONSP-INTERNAL-COVER-FADE-EASING: cubic-bezier(.23,1.12,.95,.9);

--ONSP-INTERNAL-PLAYER-FONT-BASE: inherit;
--ONSP-INTERNAL-PLAYER-SIZE-WIDTH: 15em;
--ONSP-INTERNAL-PLAYER-PADDING-INFO: 0.28em;
--ONSP-INTERNAL-PLAYER-MEDIA-CORNERS: 0.60em;
--ONSP-INTERNAL-PLAYER-PROGRESS-CORNERS: 1em;
--ONSP-INTERNAL-PLAYER-GAP: 0.5em;

--ONSP-INTERNAL-COVER-SCALABLE-MAX-HEIGHT: 550px;

--ONSP-INTERNAL-PLAYER-COLOR-PROGRESS: #FFF;
--ONSP-INTERNAL-PLAYER-COLOR-PROGRESS-BG: #ffffff26;
Expand Down Expand Up @@ -262,29 +267,70 @@
0%, 10%, 20% { transform: translateX(0px); }
80%, 90%, 100% { transform: translateX(calc(0px - var(--ONSP-INTERNAL-SCROLLER-SIZE-SUBTITLE))); }
}

.ONSP-Base .player .swappable {
min-height: var(--ONSP-INTERNAL-PLAYER-SIZE-WIDTH);
height: var(--ONSP-INTERNAL-PLAYER-SIZE-WIDTH);
display: block;
position: relative;
width: var(--ONSP-INTERNAL-PLAYER-SIZE-WIDTH);
overflow: hidden;
border-radius: var(--ONSP-INTERNAL-PLAYER-MEDIA-CORNERS);
}
.ONSP-Base .player .swappable .cover-hidden {
opacity: 0;
.ONSP-Base .player .swappable.canvas-scale {
transition: height var(--ONSP-INTERNAL-COVER-FADE-TIME) var(--ONSP-INTERNAL-COVER-FADE-EASING);
max-height: var(--ONSP-INTERNAL-COVER-SCALABLE-MAX-HEIGHT);
}
.ONSP-Base .player .swappable .cover-swapping {
/* While the image is loading this class is applied to the last image */
.ONSP-Base .player .swappable.canvas-scale .media {
top: 50%;
transform: translateY(-50%);
height: auto;
}
.ONSP-Base .player .swappable .cover {
position: absolute;
display: block;
.ONSP-Base .player .swappable.canvas-contain .media {
height: 100%;
width: auto;
left: 0;
right: 0;
margin: 0 auto;
}
.ONSP-Base .player .swappable.canvas-cover .media {
width: 100%;
background-size: cover;
transition: var(--ONSP-INTERNAL-LOWPOWER-COVER);
top: 50%;
transform: translateY(-50%);
}
.ONSP-Base .player .swappable .media {
width: 100%;
opacity: 0;
z-index: 0;
transition: opacity var(--ONSP-INTERNAL-COVER-FADE-TIME) var(--ONSP-INTERNAL-COVER-FADE-EASING);
position: absolute;
border-radius: var(--ONSP-INTERNAL-PLAYER-MEDIA-CORNERS);
z-index: 2;
background-position: center;
/* box-shadow: 0px 4px 12px 8px rgb(0 0 0 / 25%); */
overflow: hidden;
}
.ONSP-Base .player .swappable.canvas-overlays::before {
content: "";
width: 3em;
height: 3em;
bottom: 0.6em;
left: 0.6em;
position: absolute;
opacity: 0;
border-radius: 0.32em;
z-index: 12;
transition: opacity var(--ONSP-INTERNAL-COVER-FADE-TIME) var(--ONSP-INTERNAL-COVER-FADE-EASING);
background-image: var(--ONSP-INTERNAL-CANVAS-ALBUM);
background-size: cover;
background-repeat: no-repeat;
}
.ONSP-Base .player .swappable.canvas-overlays:has(video.top)::before {
opacity: 0.8;
}
.ONSP-Base .player .swappable .top {
opacity: 1;
z-index: 10;
}


.ONSP-Base .player .footer {
display: flex;
flex-flow: column nowrap;
Expand Down
53 changes: 46 additions & 7 deletions node_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ module.exports = NodeHelper.create({
this.isPlayerInTransit = 0;
// Configuration sent to the helper
this.preferences = undefined;
// Use a identifier to filter socket-io retries.
// Use a identifier to filter socket-io retries
this.appendableId = undefined;
this.serversideId = Date.now().toString(16);

// Canvas url and to which item it corresponds to
this.savedCanvasUrl = false;
this.savedCanvasUri = "";
},

socketNotificationReceived: function (notification, payload) {
Expand Down Expand Up @@ -130,8 +134,17 @@ module.exports = NodeHelper.create({

if (data && data.item) {
// CASE 1 The data is OK and there is an ITEM in the player
let isTrack =
const isTrack =
data.currently_playing_type === "track" ? true : false;

const itemImages =
this.processImages(
(isTrack ? data.item.album.images : data.item.show.images) || []);

// Add a canvas object if enabled
if (isTrack && this.preferences.useCanvas && (this.lastMediaUri !== data.item.uri))
this.statelessGetCanvas(data.item.uri, itemImages)

let payload = {
/* Player data */
playerIsPlaying: data.is_playing,
Expand All @@ -140,13 +153,11 @@ module.exports = NodeHelper.create({
playerShuffleState: data.shuffle_state,
playerRepeatState: data.repeat_state,
/* Item generics */
itemId: data.item.id,
itemUri: data.item.uri,
itemName: data.item.name,
itemDuration: data.item.duration_ms,
itemImages: this.processImages(
(isTrack ? data.item.album.images : data.item.show.images) ||
[],
),
itemImages,
/* Item specifics (Some are not used yet) */
itemAlbum: isTrack ? data.item.album.name : null,
itemPublisher: isTrack ? null : data.item.show.publisher,
Expand All @@ -162,6 +173,8 @@ module.exports = NodeHelper.create({
deviceVolume: data.device.volume_percent,
deviceIsPrivate: data.device.is_private_session,
deviceId: data.device.id,
/* Special canvas sync */
canvas: data.item.uri === this.savedCanvasUri ? this.savedCanvasUrl : false,
/* Special status sync */
statusIsPlayerEmpty: false,
statusIsNewSong:
Expand Down Expand Up @@ -272,12 +285,38 @@ module.exports = NodeHelper.create({
} catch (error) {
console.warn(
"[\x1b[35mMMM-OnSpotify\x1b[0m] >> \x1b[41m\x1b[37m %s \x1b[0m ",
"Error fetching data",
"Error fetching data (OOB)",
error,
);
}
},

async statelessGetCanvas(uri, itemImages) {
try {
// use then to prevent context issue
this.fetcher.getCanvas(uri).then(canvas => {
console.log("[CANVAS DATA]", JSON.stringify(canvas), typeof x)

if (canvas.canvasesList.length == 1 && canvas.canvasesList[0].canvasUrl.endsWith('.mp4')) {
const item = canvas.canvasesList[0];
this.savedCanvasUrl = item.canvasUrl;
this.savedCanvasUri = item.trackUri;

this.sendSocketNotification("UPDATE_CANVAS", {
itemUri: item.trackUri,
url: item.canvasUrl,
itemImages: itemImages
});
}
});
} catch (error) {
console.warn(
"[\x1b[35mMMM-OnSpotify\x1b[0m] >> \x1b[41m\x1b[37m %s \x1b[0m ",
"Error fetching cover data (OOB)",
error,
);
}
},
isCorrectIdOrRefresh(rcvd) {
if (rcvd !== this.appendableId) {
if (typeof this.appendableId === "undefined") {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"cookie-parser": "^1.4.6",
"dompurify": "^3.0.6",
"express": "^4.18.2",
"google-protobuf": "^3.21.2",
"node-fetch": "^2.6.9",
"querystring": "^0.2.1",
"request": "^2.88.2"
Expand Down
Loading