Skip to content

Commit

Permalink
Handle video content type - fixes #61
Browse files Browse the repository at this point in the history
  • Loading branch information
Inrixia committed Aug 23, 2024
1 parent cca8cef commit 0acdd6a
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 147 deletions.
41 changes: 12 additions & 29 deletions plugins/CoverTheme/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { intercept } from "@neptune";
import getPlaybackControl from "@inrixia/lib/getPlaybackControl";
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";
import { MediaItemCache } from "@inrixia/lib/Caches/MediaItemCache";
import Vibrant from "node-vibrant";
import { getStyle, setStyle } from "@inrixia/lib/css/setStyle";
import { settings } from "./Settings";
Expand All @@ -10,22 +10,19 @@ let prevSong: string | undefined;
let prevCover: string | undefined;
let vars: string[] = [];

const getCoverUrl = (id: string) =>
"https://resources.tidal.com/images/" +
id.split("-").join("/") +
"/640x640.jpg?cors";
const getCoverUrl = (id: string) => "https://resources.tidal.com/images/" + id.split("-").join("/") + "/640x640.jpg?cors";

async function updateBackground(productId: string) {
if (prevSong === productId) return;
prevSong = productId;

const track = await TrackItemCache.ensure(productId);
if (!track || !track.album?.cover) return;
const mediaItem = await MediaItemCache.ensure(productId);
if (!mediaItem || !mediaItem.album?.cover) return;

if (prevCover === track.album.cover) return;
prevCover = track.album.cover;
if (prevCover === mediaItem.album.cover) return;
prevCover = mediaItem.album.cover;

const cover = getCoverUrl(track.album.cover);
const cover = getCoverUrl(mediaItem.album.cover);
const palette = await Vibrant.from(cover).getPalette();

for (const [colorName, color] of Object.entries(palette)) {
Expand All @@ -37,10 +34,7 @@ async function updateBackground(productId: string) {

if (!vars.includes(variableName)) vars.push(variableName);

document.documentElement.style.setProperty(
variableName,
color.rgb.join(", ")
);
document.documentElement.style.setProperty(variableName, color.rgb.join(", "));
}
}

Expand All @@ -49,15 +43,9 @@ function onTransition([track]: any[]) {
if (id) updateBackground(id);
}

const unloadPrefill = intercept(
"playbackControls/PREFILL_MEDIA_PRODUCT_TRANSITION",
onTransition
);
const unloadPrefill = intercept("playbackControls/PREFILL_MEDIA_PRODUCT_TRANSITION", onTransition);

const unloadTransition = intercept(
"playbackControls/MEDIA_PRODUCT_TRANSITION",
onTransition
);
const unloadTransition = intercept("playbackControls/MEDIA_PRODUCT_TRANSITION", onTransition);

export function updateCSS() {
if (settings.transparentTheme) {
Expand Down Expand Up @@ -95,10 +83,7 @@ export function updateCSS() {
};

const gradients = Object.entries(positions)
.map(
([position, variable]) =>
`radial-gradient(ellipse at ${position}, rgb(var(--cover-${variable}), 0.5), transparent 70%)`
)
.map(([position, variable]) => `radial-gradient(ellipse at ${position}, rgb(var(--cover-${variable}), 0.5), transparent 70%)`)
.join(", ");

setStyle(`body{background-image:${gradients};}`, "backgroundGradient");
Expand All @@ -115,9 +100,7 @@ export const onUnload = () => {
unloadPrefill();
unloadTransition();
getStyle("coverTheme")?.remove();
vars.forEach((variable) =>
document.documentElement.style.removeProperty(variable)
);
vars.forEach((variable) => document.documentElement.style.removeProperty(variable));
prevSong = undefined;
prevCover = undefined;
};
48 changes: 17 additions & 31 deletions plugins/DiscordRPC/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { settings } from "./Settings";
export { Settings } from "./Settings";

import getPlaybackControl from "@inrixia/lib/getPlaybackControl";
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";
import { MediaItemCache } from "@inrixia/lib/Caches/MediaItemCache";
import { onRpcCleanup, updateRPC } from "@inrixia/lib/nativeBridge/discordRPC";
import type { SetActivity } from "@xhayper/discord-rpc";

Expand All @@ -17,19 +17,16 @@ const formatLongString = (s?: string) => {
if (s.length < 2) s += " ";
return s.length >= STR_MAX_LEN ? s.slice(0, STR_MAX_LEN - 3) + "..." : s;
};
const getMediaURLFromID = (id?: string, path = "/1280x1280.jpg") =>
id
? "https://resources.tidal.com/images/" + id.split("-").join("/") + path
: undefined;
const getMediaURLFromID = (id?: string, path = "/1280x1280.jpg") => (id ? "https://resources.tidal.com/images/" + id.split("-").join("/") + path : undefined);

let previousActivity: string | undefined;

export const onTimeUpdate = async (currentTime?: number) => {
const { playbackContext, playbackState } = getPlaybackControl();
if (!playbackState) return;

const track = await TrackItemCache.ensure(playbackContext?.actualProductId);
if (track === undefined) return;
const mediaItem = await MediaItemCache.ensure(playbackContext?.actualProductId!);
if (mediaItem === undefined) return;

const loading = currentTime === 0 && previousActivity;
const playing = playbackState !== "NOT_PLAYING" || loading;
Expand All @@ -41,7 +38,7 @@ export const onTimeUpdate = async (currentTime?: number) => {
if (settings.displayPlayButton)
activity.buttons = [
{
url: `https://tidal.com/browse/track/${track.id}?u`,
url: `https://tidal.com/browse/${mediaItem.contentType}/${mediaItem.id}?u`,
label: "Play Song",
},
];
Expand All @@ -52,35 +49,29 @@ export const onTimeUpdate = async (currentTime?: number) => {
activity.smallImageText = "Paused";
} else {
// Playback/Time
if (track.duration !== undefined && currentTime !== undefined) {
if (mediaItem.duration !== undefined && currentTime !== undefined) {
activity.startTimestamp = Math.floor(Date.now() / 1000);
activity.endTimestamp = Math.floor(
(Date.now() + (track.duration - currentTime) * 1000) / 1000
);
activity.endTimestamp = Math.floor((Date.now() + (mediaItem.duration - currentTime) * 1000) / 1000);
}

// Artist image
const artist = track.artist ?? track.artists?.[0];
const artist = mediaItem.artist ?? mediaItem.artists?.[0];
if (artist && settings.displayArtistImage) {
activity.smallImageKey = getMediaURLFromID(
artist.picture,
"/320x320.jpg"
);
activity.smallImageKey = getMediaURLFromID(artist.picture, "/320x320.jpg");
activity.smallImageText = formatLongString(artist.name);
}
}

// Album
if (track.album !== undefined) {
activity.largeImageKey = getMediaURLFromID(track.album.cover);
activity.largeImageText = formatLongString(track.album.title);
if (mediaItem.album !== undefined) {
activity.largeImageKey = getMediaURLFromID(mediaItem.album.cover);
activity.largeImageText = formatLongString(mediaItem.album.title);
}

// Title/Artist
const artist =
track.artists?.map((a) => a.name).join(", ") ?? "Unknown Artist";
const artist = mediaItem.artists?.map((a) => a.name).join(", ") ?? "Unknown Artist";

activity.details = formatLongString(track.title);
activity.details = formatLongString(mediaItem.title);
activity.state = formatLongString(artist);

// Check if the activity actually changed
Expand All @@ -91,14 +82,9 @@ export const onTimeUpdate = async (currentTime?: number) => {
updateRPC(activity);
};

const onUnloadTimeUpdate = intercept(
"playbackControls/TIME_UPDATE",
([newTime]) => {
onTimeUpdate(newTime).catch(
trace.msg.err.withContext("Failed to update")
);
}
);
const onUnloadTimeUpdate = intercept("playbackControls/TIME_UPDATE", ([newTime]) => {
onTimeUpdate(newTime).catch(trace.msg.err.withContext("Failed to update"));
});

onTimeUpdate().catch(trace.msg.err.withContext("Failed to update"));
export const onUnload = () => {
Expand Down
6 changes: 3 additions & 3 deletions plugins/LastFM/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { PlaybackState } from "neptune-types/tidal";
import { Tracer } from "@inrixia/lib/trace";
const trace = Tracer("[last.fm]");

import { ExtendedTrackItem } from "@inrixia/lib/Caches/ExtendedTrackItem";
import { ExtendedMediaItem } from "@inrixia/lib/Caches/ExtendedTrackItem";
import { debounce } from "@inrixia/lib/debounce";
import safeUnload from "@inrixia/lib/safeUnload";
import getPlaybackControl from "@inrixia/lib/getPlaybackControl";
Expand Down Expand Up @@ -86,7 +86,7 @@ const intercepters = [
];

type CurrentTrack = {
extTrackItem: ExtendedTrackItem;
extTrackItem: ExtendedMediaItem;
playbackContext: PlaybackContext;
playbackStart: number;
metaTags: MetaTags;
Expand All @@ -97,7 +97,7 @@ const getCurrentTrack = async (playbackContext?: PlaybackContext): Promise<Curre

playbackContext ??= getPlaybackControl()?.playbackContext;
if (playbackContext === undefined) throw new Error("PlaybackContext is undefined");
const extTrackItem = await ExtendedTrackItem.current(playbackContext);
const extTrackItem = await ExtendedMediaItem.current(playbackContext);
if (extTrackItem === undefined) throw new Error("Failed to get extTrackItem");

const metaTags = await makeTags(extTrackItem);
Expand Down
6 changes: 3 additions & 3 deletions plugins/RealMAX/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";
import { MediaItemCache } from "@inrixia/lib/Caches/MediaItemCache";
import { actions, intercept, store } from "@neptune";
import { debounce } from "@inrixia/lib/debounce";

Expand Down Expand Up @@ -26,7 +26,7 @@ const unloadIntercept = intercept(
const maxItem = await MaxTrack.getMaxTrack(queueId);
if (maxItem === false) return;
if (maxItem.id !== undefined && nextQueueId !== maxItem.id) {
await TrackItemCache.ensure(maxItem.id);
await MediaItemCache.ensure(maxItem.id);
if (settings.displayInfoPopups) trace.msg.log(`Found Max quality for ${maxItem.title}! Adding to queue and skipping...`);
actions.playQueue.addNext({ mediaItemIds: [maxItem.id], context: { type: "user" } });
actions.playQueue.moveNext();
Expand Down Expand Up @@ -62,7 +62,7 @@ ContextMenu.onOpen(async (contextSource, contextMenu, trackItems) => {
const trackId = trackItem.id!;
const maxItem = await MaxTrack.getMaxTrack(trackItem.id).catch(trace.msg.err.withContext(`Failed to create ${sourceName}`));
if (maxItem !== false && maxItem?.id !== undefined) {
if ((await TrackItemCache.ensure(trackId)) !== undefined) {
if ((await MediaItemCache.ensure(trackId)) !== undefined) {
trace.msg.log(`Found Max quality for ${maxItem.title} in ${sourceName}! ${index}/${trackItems.length - 1} done.`);
trackIds.push(+maxItem.id);
maxIdsFound++;
Expand Down
4 changes: 2 additions & 2 deletions plugins/SongDownloader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PlaybackInfoCache } from "@inrixia/lib/Caches/PlaybackInfoCache";
import { openDialog, saveDialog } from "@inrixia/lib/nativeBridge";
import { startTrackDownload, getDownloadProgress } from "@inrixia/lib/nativeBridge/request";
import { makeTags } from "@inrixia/lib/makeTags";
import { ExtendedTrackItem } from "@inrixia/lib/Caches/ExtendedTrackItem";
import { ExtendedMediaItem } from "@inrixia/lib/Caches/ExtendedTrackItem";
import { MaxTrack } from "@inrixia/lib/MaxTrack";
import { AudioQuality } from "@inrixia/lib/AudioQualityTypes";
import { dialog } from "electron";
Expand Down Expand Up @@ -111,7 +111,7 @@ const downloadTrack = async (trackItem: TrackItem, updateMethods: ButtonMethods,

updateMethods.set("Fetching playback info & tags...");
const playbackInfo = PlaybackInfoCache.ensure(trackId, settings.desiredDownloadQuality);
const metaTags = makeTags((await ExtendedTrackItem.get(trackId))!);
const metaTags = makeTags((await ExtendedMediaItem.get(trackId))!);
const pathInfo = parseFileName(await metaTags, await playbackInfo);

console.log(pathInfo);
Expand Down
6 changes: 3 additions & 3 deletions plugins/TidalTags/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { settings } from "./Settings";

import { isElement } from "./lib/isElement";
import { setInfoColumnHeaders, setInfoColumns } from "./setInfoColumns";
import { TrackItemCache } from "@inrixia/lib/Caches/TrackItemCache";
import { MediaItemCache } from "@inrixia/lib/Caches/MediaItemCache";
import { PlaybackContext } from "@inrixia/lib/AudioQualityTypes";
import safeUnload from "@inrixia/lib/safeUnload";

Expand Down Expand Up @@ -42,8 +42,8 @@ const updateTrackRows = async (trackRows: NodeListOf<Element>) => {
const trackId = trackRow.getAttribute("data-track-id");
if (trackId == null) return;

const trackItem = await TrackItemCache.ensure(trackId);
if (trackItem?.contentType !== "track") continue;
const trackItem = await MediaItemCache.ensureTrack(trackId);
if (trackItem === undefined) continue;

if (settings.showTags) setQualityTags(trackRow, trackId, trackItem);
if (settings.displayInfoColumns) setInfoColumns(trackRow, trackId, trackItem);
Expand Down
5 changes: 4 additions & 1 deletion plugins/TidalTags/src/setFLACInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ export const setFLACInfo = async ([{ playbackContext }]: [{ playbackContext?: Pl
}

try {
await TrackInfoCache.ensure(playbackContext);
if ((await TrackInfoCache.ensure(playbackContext)) === undefined) {
flacInfoElem.textContent = `Unsupported Content...`;
return;
}
await TrackInfoCache.register(playbackContext.actualProductId, playbackContext.actualAudioQuality, ({ sampleRate, bitDepth, bitrate }) => {
flacInfoElem.textContent = "";
if (!!sampleRate) flacInfoElem.textContent += `${sampleRate / 1000}kHz `;
Expand Down
12 changes: 6 additions & 6 deletions plugins/_lib/Caches/ExtendedTrackItem.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { ItemId, TrackItem, Album } from "neptune-types/tidal";
import type { ItemId, Album } from "neptune-types/tidal";
import type { PlaybackContext } from "../AudioQualityTypes";
import { MusicBrainz, Release, Recording } from "../api/musicbrainz";
import { TrackItemCache } from "./TrackItemCache";
import { MediaItem, MediaItemCache } from "./MediaItemCache";
import { AlbumCache } from "./AlbumCache";
import { libTrace } from "../trace";
import getPlaybackControl from "../getPlaybackControl";

export class ExtendedTrackItem {
export class ExtendedMediaItem {
private _album?: Album;
private _recording?: Recording;
private _releaseAlbum?: Release;

private static readonly _cache: Record<ItemId, ExtendedTrackItem> = {};
private constructor(public readonly trackId: ItemId, public readonly trackItem: TrackItem) {}
private static readonly _cache: Record<ItemId, ExtendedMediaItem> = {};
private constructor(public readonly trackId: ItemId, public readonly trackItem: MediaItem) {}

public static current(playbackContext?: PlaybackContext) {
playbackContext ??= getPlaybackControl()?.playbackContext;
Expand All @@ -22,7 +22,7 @@ export class ExtendedTrackItem {

public static async get(trackId?: ItemId) {
if (trackId === undefined) return undefined;
const trackItem = await TrackItemCache.ensure(trackId);
const trackItem = await MediaItemCache.ensure(trackId);
if (trackItem === undefined) return undefined;
return this._cache[trackId] ?? (this._cache[trackId] = new this(trackId, trackItem));
}
Expand Down
60 changes: 60 additions & 0 deletions plugins/_lib/Caches/MediaItemCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { store } from "@neptune";
import type { TrackItem, MediaItem as TidalMediaItem, ItemId, VideoItem } from "neptune-types/tidal";
import { interceptPromise } from "../intercept/interceptPromise";
import type { PlaybackContext } from "../AudioQualityTypes";
import getPlaybackControl from "../getPlaybackControl";

import { libTrace } from "../trace";

export type MediaItem = TrackItem | VideoItem;
export class MediaItemCache {
private static readonly _cache: Record<ItemId, MediaItem> = {};
public static current(playbackContext?: PlaybackContext) {
playbackContext ??= getPlaybackControl()?.playbackContext;
if (playbackContext?.actualProductId === undefined) return undefined;
return this.ensure(playbackContext.actualProductId);
}
public static async ensureTrack(itemId?: ItemId) {
const mediaItem = await this.ensure(itemId);
if (mediaItem?.contentType === "track") return mediaItem;
return undefined;
}
public static async ensureVideo(itemId?: ItemId) {
const mediaItem = await this.ensure(itemId);
if (mediaItem?.contentType === "video") return mediaItem;
return undefined;
}
public static async ensure(itemId?: ItemId) {
if (itemId === undefined) return undefined;

let mediaItem = this._cache[itemId];
if (mediaItem !== undefined) return mediaItem;

const mediaItems: Record<number, TidalMediaItem> = store.getState().content.mediaItems;
for (const itemId in mediaItems) {
const item = mediaItems[itemId]?.item;
this._cache[itemId] = item;
}

if (this._cache[itemId] === undefined) {
const currentPage = window.location.pathname;

const loadedTrack = await interceptPromise(() => neptune.actions.router.replace(<any>`/track/${itemId}`), ["page/IS_DONE_LOADING"], [])
.then(() => true)
.catch(libTrace.warn.withContext(`TrackItemCache.ensure failed to load track ${itemId}`));
// If we fail to load the track, maybe its a video, try that instead as a last ditch attempt
if (!loadedTrack) {
await interceptPromise(() => neptune.actions.router.replace(<any>`/video/${itemId}`), ["page/IS_DONE_LOADING"], []).catch(
libTrace.warn.withContext(`TrackItemCache.ensure failed to load video ${itemId}`)
);
}
neptune.actions.router.replace(<any>currentPage);

const mediaItems: Record<number, TidalMediaItem> = store.getState().content.mediaItems;
const trackItem = mediaItems[+itemId]?.item;
this._cache[itemId] = trackItem;
}

return this._cache[itemId];
}
}
Loading

0 comments on commit 0acdd6a

Please sign in to comment.