Skip to content
23 changes: 23 additions & 0 deletions websites/M/Monochrome/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://schemas.premid.app/metadata/1.16",
"apiVersion": 1,
"author": {
"id": "838652741965971517",
"name": "p1nkhamster"
},
"service": "Monochrome",
"description": {
"en": "Monochrome is an open-source, privacy-respecting, ad-free TIDAL web UI, built on top of Hi-Fi."
},
"url": "monochrome.samidy.com",
"regExp": "^https?[:][/][/](monochrome[.]samidy[.]com|monochrome[.]tf|monochrome[.]prigoana[.]com|monochrome-back[.]pages[.]dev)[/]",
"version": "1.0.0",
"logo": "https://i.imgur.com/XHf3H8Y.png",
"thumbnail": "https://i.imgur.com/LeV2zuA.png",
"color": "#000000",
"category": "music",
"tags": [
"music",
"tidal"
]
}
93 changes: 93 additions & 0 deletions websites/M/Monochrome/presence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ActivityType } from 'premid'

declare const Presence: any

// Static Asset Configuration
enum ActivityAssets {
Logo = 'https://i.imgur.com/XHf3H8Y.png',
Play = 'https://i.imgur.com/ryNutI5.png',
Pause = 'https://i.imgur.com/TlMwR5i.png',
}

const presence = new Presence({
clientId: '1459594619972096248',
})

presence.on('UpdateData', async () => {
// 1. DYNAMIC IMAGE LOGIC
// Default to the static logo
let currentLargeImage: string = ActivityAssets.Logo

// standard mediaSession check for high-res artwork
const artwork = navigator.mediaSession?.metadata?.artwork

if (artwork && artwork.length > 0) {
// Select the last image in the array (typically the highest resolution)
const coverUrl = artwork[artwork.length - 1]?.src
if (coverUrl) {
currentLargeImage = coverUrl
}
}

// 2. INITIALIZE ACTIVITY DATA
const presenceData: any = {
type: ActivityType.Listening,
largeImageKey: currentLargeImage,
largeImageText: 'Listening on Monochrome',
// Default small icon (overwritten below if paused)
smallImageKey: ActivityAssets.Play,
smallImageText: 'Playing',
}

// 3. TEXT STRATEGY (Browser Tab)
// Parses "Song - Artist" or "Song • Artist" from the document title
const tabTitle = document.title || ''
let separator = ''

if (tabTitle.includes(' - '))
separator = ' - '
else if (tabTitle.includes(' • '))
separator = ' • '

if (separator) {
const parts = tabTitle.split(separator)
presenceData.details = parts[0]?.trim() || 'Unknown Song'
presenceData.state = parts.slice(1).join(separator).trim() || 'Unknown Artist'
}
else {
// Fallback for non-standard titles
presenceData.details = 'Monochrome'
presenceData.state = 'Listening...'
}

// 4. AUDIO STATUS & TIMESTAMPS
const mediaElement = document.querySelector('audio')

if (mediaElement) {
if (!mediaElement.paused) {
// -- PLAYING STATE --
presenceData.smallImageKey = ActivityAssets.Play
presenceData.smallImageText = 'Playing'

// Calculate timestamps using native Date.now() for accuracy
const now = Date.now()
presenceData.startTimestamp = now - (mediaElement.currentTime * 1000)

// Only set endTimestamp if duration is finite and positive
if (mediaElement.duration && Number.isFinite(mediaElement.duration) && mediaElement.duration > 0) {
presenceData.endTimestamp = now + ((mediaElement.duration - mediaElement.currentTime) * 1000)
}
}
else {
// -- PAUSED STATE --
presenceData.smallImageKey = ActivityAssets.Pause
presenceData.smallImageText = 'Paused'
// Note: We do not set timestamps here, effectively hiding the time bar
}

presence.setActivity(presenceData)
}
else {
presence.clearActivity()
}
})
Loading