Deezer PiP - deezer.com

Adds PiP features to Deezer

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Deezer PiP - deezer.com
// @namespace   https://github.com/Thibb1
// @match       https://www.deezer.com/*
// @grant       none
// @version     1.0.1
// @author      Thibb1
// @description Adds PiP features to Deezer
// @license     GPL
// ==/UserScript==

let timer;
function debounce(func) {
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => { func.apply(this, args); }, 1000);
    };
}
const CLICK_TIMEOUT = 200;
const NEXT = '.player-controls [data-testid="StepForwardIcon"]';
const PREVIOUS = '.player-controls [data-testid="StepBackwardIcon"]';
const PAUSE = '.player-controls [data-testid="PauseIcon"]'
const PLAY = '.player-controls [data-testid="PlayIcon"]'
const QUEUELIST = '.player-full.player-lyrics-full';
const QUEUE = '.player-options > ul > li:nth-child(2) > button';
const FAVORITES = ".sidebar-nav-list > li:nth-child(5) > a";
const SHUFFLE = '[data-testid="user-shuffle-button"]';
const MIX = '[data-testid="NoteWaveIcon"]';
const REMOVE_FAV = '.player-bottom [data-testid="HeartFillIcon"]'
const ADD_FAV = '.player-bottom [data-testid="HeartIcon"]'

const STYLE_TEXT = `
    body {
        background: #212121;
        color: #fff;
        font-weight: 700;
    }
    button {
        flex: 1;
        height: 45px;
        font-size: 16px;
        box-shadow: 0px 0px 20px -20px;
        border-radius: 5px;
        cursor: pointer;
        transition: all 0.2s ease-in-out 0ms;
        user-select: none;
    }
    button:hove {
        box-shadow: 0px 0px 20px -18px;
    }
    button:active {
        transform: scale(0.95);
    }
    .shuffle {
        background: #ca2a36;
        color: #fff;
    }
    .main > div {
        flex: 1;
    }
    .main {
        flex-direction: column;
        width: 90%;
    }
    body, p, div {
        display: flex;
        gap: 8px;
        justify-content: center;
    }
`;


function getTitle() {
    return [...document.querySelectorAll('.track-link')].map((e) => e.textContent).join(' - ');
}

function createButton(text, onClick) {
    const button = document.createElement('button');
    button.textContent = text;
    button.addEventListener('click', onClick);
    return button;
}

async function onClick() {
    const pipWindow = await documentPictureInPicture.requestWindow({ width: 284, height: 284 });
    const main = pipWindow.document.createElement('div');
    const audioController = document.createElement("div");
    const nextSong = createButton('Next', () => {
        document.querySelector(NEXT).parentElement.click();
    });
    const previousSong = createButton('Previous', () => {
        document.querySelector(PREVIOUS).parentElement.click();
    });
    const playPause = createButton('', () => {
        const play = document.querySelector(PLAY);
        const pause = document.querySelector(PAUSE);
        if (play) {
            play.parentElement.click();
            playPause.textContent = 'Pause';
        } else {
            pause.parentElement.click();
            playPause.textContent = 'Play';
        }
    });
    const playMix = createButton('Launch Track mix', () => {
        const queueOpened = document.querySelector(QUEUELIST);
        if (!queueOpened) {
            document.querySelector(QUEUE).click();
        }
        setTimeout(() => {
            document.querySelector(`${QUEUELIST} [aria-selected=true] [aria-haspopup]`).click();
            document.querySelector(MIX).closest('button').click();
            if (!queueOpened) {
                document.querySelector(QUEUE).click();
            }
        }, CLICK_TIMEOUT);
    });
    const shuffle = createButton('Shuffle my music', () => {
        document.querySelector(FAVORITES).click();
        setTimeout(() => {
            document.querySelector(SHUFFLE).click();
        }, CLICK_TIMEOUT);
    });
    const toggleFav = createButton('', () => {
        const fav = document.querySelector(ADD_FAV);
        if (fav) {
            fav.parentElement.click();
            toggleFav.textContent = 'Remove from favorites';
        } else {
            document.querySelector(REMOVE_FAV).parentElement.click();
            toggleFav.textContent = 'Add to favorites';
        }
    });
    const songTitle = pipWindow.document.createElement('p');
    const style = pipWindow.document.createElement('style');
    main.classList.add('main');
    shuffle.classList.add('shuffle');
    const getText = () => {
        songTitle.textContent = getTitle();
        playPause.textContent = document.querySelector(PLAY) ? 'Play' : 'Pause';
        toggleFav.textContent = document.querySelector(REMOVE_FAV) ? 'Remove from favorites' : 'Add to favorites';
    }
    getText();
    audioController.appendChild(previousSong);
    audioController.appendChild(playPause);
    audioController.appendChild(nextSong);
    main.appendChild(audioController);
    main.appendChild(toggleFav);
    main.appendChild(playMix);
    main.appendChild(shuffle);
    main.appendChild(songTitle);
    pipWindow.document.body.appendChild(main);
    style.innerHTML = STYLE_TEXT;
    pipWindow.document.head.appendChild(style);
    const observer = new MutationObserver(() => getText());
    observer.observe(document.body, {
        childList: true,
        subtree: true,
    });
}

const observer = new MutationObserver((mutations) => {
    debounce(() => {
        observer.disconnect();
        const pipButton = document.createElement('button');
        pipButton.textContent = 'PiP';
        pipButton.addEventListener('click', onClick);
        pipButton.classList.add('svg-icon-group-btn');
        document.querySelector('.player-options > ul > li:nth-child(1) > ul').appendChild(pipButton);
    })();
});

window.onload = () => {
    observer.observe(document.body, {
        childList: true,
        subtree: true,
    });
};