by VALSZE
Memuat data dari server...
Top

Live Spotify Ticker di WordPress (Drama Dua VPS)

Live Spotify Ticker di WordPress (Drama Dua VPS)

Pukul 21 lebih. Melamun menatap layar sambil mendengarkan Spotify di background. Mau apa ya… Harus terus sibuk sampai lelah. Tiba-tiba terbersit keinginan untuk membuat marquee sederhana di bawah menu yang menampilkan lagu apa yang aku mainkan di Spotify-ku. Waktu itu kupikir, sederhana, akan seberapa sulit sih?

Boy, I couldn’t be so wrong.

1. Ide dasar: plugin kecil, REST API, dan shortcode

Aku mulai dengan membuat plugin sederhana bernama Spotify Live Ticker. Struktur dasarnya:

  • Sebuah class PHP yang:
    • Menambahkan halaman pengaturan di Settings → Spotify Live Ticker.
    • Menyimpan Client ID, Client Secret, dan Refresh Token.
    • Membuat REST API endpoint untuk mengambil lagu yang sedang diputar.
    • Menyediakan shortcode spotify_live_ticker.
  • Satu file JS untuk memanggil REST API tadi dan mengupdate teks di front-end.
  • Sedikit CSS untuk tampilan ticker.

Kurang lebih kerangka plugin-nya seperti ini:

<?php
/**
 * Plugin Name: Spotify Live Ticker
 * Description: Tampilkan live ticker lagu yang sedang diputar di Spotify dengan shortcode [spotify_live_ticker].
 * Version: 1.0.0
 * Author: V
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class Spotify_Live_Ticker {

    public function __construct() {
        // Menu & settings
        add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
        add_action( 'admin_init', array( $this, 'register_settings' ) );

        // Shortcode
        add_shortcode( 'spotify_live_ticker', array( $this, 'render_shortcode' ) );

        // Scripts
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );

        // REST API endpoint
        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
    }

    public function enqueue_scripts() {
        wp_register_script(
            'spotify-live-ticker',
            plugins_url( 'assets/js/spotify-live-ticker.js', __FILE__ ),
            array(),
            '1.0.4',
            true
        );

        wp_localize_script(
            'spotify-live-ticker',
            'SpotifyLiveTicker',
            array(
                'restUrl'         => esc_url_raw( rest_url( 'spotify-live-ticker/v1/current' ) ),
                'defaultInterval' => 15000,
            )
        );

        wp_enqueue_script( 'spotify-live-ticker' );
    }

    public function render_shortcode( $atts ) {
        $atts = shortcode_atts(
            array(
                'refresh_interval' => 15000,
            ),
            $atts,
            'spotify_live_ticker'
        );

        ob_start();
        ?>
        <div class="spotify-live-ticker-container">
            <div id="spotify-live-ticker" class="spotify-live-ticker">
                <span class="spotify-live-ticker-text">Loading current track...</span>
            </div>
        </div>
        <script>
            window.SLT_REFRESH_INTERVAL = <?php echo (int) $atts['refresh_interval']; ?>;
        </script>
        <?php
        return ob_get_clean();
    }

    public function register_rest_routes() {
        register_rest_route(
            'spotify-live-ticker/v1',
            '/current',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'rest_get_current_track' ),
                'permission_callback' => '__return_true',
            )
        );
    }

    // ... (fungsi lain: settings page, callback Spotify, dll)
}

new Spotify_Live_Ticker();

Endpoint REST /spotify-live-ticker/v1/current ini nanti yang dipanggil JavaScript di front-end.

2. Binding ke Spotify: OAuth, redirect URI, dan refresh token

Agar WordPress bisa baca lagu yang sedang diputar, aku pakai:

  • user-read-currently-playing
  • user-read-playback-state

di Spotify API. Flow-nya:

  1. Di Spotify Developer Dashboard, aku buat app dan mencatat:
    • Client ID
    • Client Secret
  2. Di halaman pengaturan plugin, aku generate URL authorize seperti ini:
$scope     = 'user-read-currently-playing user-read-playback-state';
$redirect  = admin_url( 'options-general.php?page=spotify-live-ticker' );

$auth_url  = 'https://accounts.spotify.com/authorize?response_type=code';
$auth_url .= '&client_id=' . urlencode( $client_id );
$auth_url .= '&scope=' . urlencode( $scope );
$auth_url .= '&redirect_uri=' . urlencode( $redirect );

3. URL redirect URI di atas harus persis didaftarkan di Spotify Dashboard, misalnya:

https://options-general.php?page=spotify-live-ticker

Saat klik Connect to Spotify, aku diarahkan ke halaman login/authorize Spotify, lalu kembali ke halaman pengaturan dengan query ?code=… Kodenya kemudian diproses di fungsi:

private function handle_spotify_callback( $code ) {
    $client_id     = get_option( self::OPTION_CLIENT_ID, '' );
    $client_secret = get_option( self::OPTION_CLIENT_SECRET, '' );
    $redirect_uri  = $this->get_redirect_uri();

    $response = wp_remote_post(
        'https://accounts.spotify.com/api/token',
        array(
            'headers' => array(
                'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $client_secret ),
                'Content-Type'  => 'application/x-www-form-urlencoded',
            ),
            'body'    => http_build_query(
                array(
                    'grant_type'   => 'authorization_code',
                    'code'         => $code,
                    'redirect_uri' => $redirect_uri,
                )
            ),
            'timeout' => 20,
        )
    );

    $body_raw = wp_remote_retrieve_body( $response );
    $body     = json_decode( $body_raw, true );

    if ( isset( $body['refresh_token'] ) ) {
        update_option( self::OPTION_REFRESH_TOKEN, sanitize_text_field( $body['refresh_token'] ) );
        // tampilkan pesan sukses di admin
    } else {
        // simpan $body_raw untuk debug di halaman pengaturan
    }
}

Secara teori, di titik ini WordPress harusnya sudah punya refresh_token dan bisa terus-menerus minta access_token baru tanpa perlu authorize lagi.

Teorinya…

3. Drama VPS pertama: ARWEN dan Error 403 Spotify

WordPressku awalnya jalan di sebuah VPS yang kuberi nama ARWEN, aku baru saja migrasi ke server ini karena alasan tertentu. Semua kodenya tampak benar, redirect berfungsi, code=... didapat, tapi refresh token tidak pernah terisi.

Garuk kepala, cabut beberapa helai rambut, waktu berlalu dengan modifikasi kode. Tetap tidak mempan, tiba-tiba aku teringat akan persoalan seorang user yang menggunakan pluginku. Tentang IP address yang terblokir. Jangan-jangan…. Kutambahkan debug untuk menyimpan raw response dari Spotify. Hasilnya ternyata bukan JSON, tapi HTML:

<h1>Error: Forbidden</h1>
<h2>Your client does not have permission to get URL <code>/api/token</code> from this server.</h2>

Jantung berdebar kencang. Ini membuatku curiga: jangan-jangan bukan kodenya yang salah, tapi koneksi ARWEN ke Spotify yang diblok. Gas cek langsung dari SSH di ARWEN:

curl -v https://accounts.spotify.com/api/token

Hasilnya:

> GET /api/token HTTP/2
< HTTP/2 403
<html><head>
<title>403 Forbidden</title>
...
<h2>Your client does not have permission to get URL <code>/api/token</code> from this server.</h2>

Jadi bahkan curl biasa pun ditolak 403.
Artinya:

  • ARWEN memang bisa resolve accounts.spotify.com dan connect TLS.
  • Tapi setiap request ke /api/token di-reject sebelum sempat masuk ke logika OAuth.

Apa pun plugin atau kode PHP yang aku tulis, hasilnya akan sama: 403 Forbidden. Sumpah serapah keluar dari mulut dan otakku di jam 22 lebih.

4. Kembali ke ARAGORN

SSH dari ARAGORN. Kemudian cURL yang sama dari ARAGORN, kali ini hasilnya:

< HTTP/2 405
<title>Error - Spotify</title>

405 artinya Method Not Allowed (wajar, karena /api/token seharusnya diakses dengan POST, bukan GET). Ini pertanda baik: setidaknya aku sekarang sudah bicara dengan Spotify yang asli, bukan diblokir oleh dia yang kusayang firewall. Lalu kucoba request POST seperti yang nanti dilakukan WordPress:

curl -v -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -u "CLIENT_ID:CLIENT_SECRET" \
  https://accounts.spotify.com/api/token

Balasannya:

{"error":"invalid_client","error_description":"Invalid client"}

Dan ini kabar terbaik. Artinya endpoint /api/token:

  • Bisa diakses dari ARAGORN.
  • Responsnya sudah JSON Spotify yang valid.

“invalid_client” wajar karena aku pakai placeholder CLIENT_ID:CLIENT_SECRET. Jadi kesimpulannya jelas:

  • ARWEN tidak bisa bicara dengan /api/token (403).
  • ARAGORN bisa bicara dengan /api/token (405 untuk GET, 400 JSON untuk POST jelek, dan JSON valid untuk POST benar).

Jadi aku memutuskan:

  • Migrasikan WordPress + plugin kembali ke ARAGORN, maaf ARWEN, aku sudah berusaha sekuat mungkin, tapi tampaknya memang kamu tidak bisa untuk memahami dan berjalan bersamaku (huhuhu)
  • Konfigurasi ulang DNS domain ke VPS ini,
  • Dan ulangi proses “Connect to Spotify”.

Begitu jalan di ARAGORN, barulah refresh_token sukses tersimpan.

5. Mengambil lagu yang sedang diputar lewat REST

Setelah refresh token tersimpan, endpoint REST kupakai untuk:

  1. Refresh access token setiap kali dibutuhkan:
$token_response = wp_remote_post(
    'https://accounts.spotify.com/api/token',
    array(
        'headers' => array(
            'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $client_secret ),
            'Content-Type'  => 'application/x-www-form-urlencoded',
        ),
        'body'    => http_build_query(
            array(
                'grant_type'    => 'refresh_token',
                'refresh_token' => $refresh_token,
            )
        ),
        'timeout' => 20,
    )
);

2. Memanggil endpoint:

https://api.spotify.com/v1/me/player/currently-playing

dan mereduksi respons ke data yang kubutuhkan:

$data = array(
    'is_playing'  => ! empty( $body['is_playing'] ),
    'track_name'  => $track['name'] ?? '',
    'artists'     => implode( ', ', $artists ),
    'album_name'  => $track['album']['name'] ?? '',
    'track_url'   => $track['external_urls']['spotify'] ?? '',
    'album_image' => $album_image,
);

Endpoint ini dikonsumsi oleh JavaScript di sisi front-end.

6. JavaScript: fetch, format teks, dan hindari reset animasi

File JS (assets/js/spotify-live-ticker.js) bertugas:

  • Memanggil REST API tiap beberapa detik.
  • Menyusun teks dengan format: 🎧 Now playing : artist – judul lagu dari album [album]
  • Menjalankan animasi marquee (teks berjalan).
  • Tidak mengulang animasi kalau lagunya masih sama (supaya judul panjang sempat kebaca sampai habis).

Inilah versi JS yang sekarang aku pakai:

(function () {
    function initTicker() {
        var container = document.getElementById('spotify-live-ticker');
        if (!container || typeof SpotifyLiveTicker === 'undefined') {
            return;
        }

        var textSpan = container.querySelector('.spotify-live-ticker-text');
        if (!textSpan) {
            return;
        }

        var refreshInterval = window.SLT_REFRESH_INTERVAL || SpotifyLiveTicker.defaultInterval || 15000;

        // Simpan teks terakhir supaya tidak reset animasi kalau lagunya masih sama
        var lastText = '';

        async function fetchCurrentTrack() {
            try {
                var response = await fetch(SpotifyLiveTicker.restUrl, {
                    method: 'GET'
                });

                if (!response.ok) {
                    throw new Error('HTTP error ' + response.status);
                }

                var data = await response.json();

                if (!data || data.error) {
                    textSpan.textContent = 'Unable to get Spotify data.';
                    lastText = textSpan.textContent;
                    return;
                }

                if (!data.is_playing) {
                    textSpan.textContent = 'Nothing is playing on Spotify right now.';
                    lastText = textSpan.textContent;
                    return;
                }

                var artist = data.artists || '';
                var track  = data.track_name || '';
                var album  = data.album_name || '';

                // Format: 🎧 Now playing : artist - judul lagu dari album [album]
                var text = '🎧 Now playing : ';

                if (artist) {
                    text += artist + ' - ';
                }

                text += track;

                if (album) {
                    text += ' dari album [' + album + ']';
                }

                // Kalau teks sama, jangan reset animasi (biar sempat kebaca)
                if (text === lastText) {
                    return;
                }

                textSpan.textContent = text;
                lastText = text;

                // Reset animasi marquee
                textSpan.style.animation = 'none';
                void textSpan.offsetWidth; // reflow
                textSpan.style.animation = null;

            } catch (e) {
                console.error('Spotify Live Ticker error:', e);
                textSpan.textContent = 'Error loading current track.';
                lastText = textSpan.textContent;
            }
        }

        // First load
        fetchCurrentTrack();

        // Interval untuk refresh
        setInterval(fetchCurrentTrack, refreshInterval);
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(initTicker, 100);
    } else {
        document.addEventListener('DOMContentLoaded', initTicker);
    }
})();

Trik lastText itu kecil, tapi efeknya besar; kalau lagunya sama, polling tetap jalan, namun animasi tidak diulang, jadi teks panjang bisa berjalan sampai akhir.

7. Menjahit ke theme: header.php & CSS

Awalnya aku mencoba memasukkan shortcode ke “Slider Shortcode” di meta box halaman, tapi hasilnya tidak muncul seperti yang aku mau (memang, aku tidak pernah mendapat apa yang aku mau dalam satu kali percobaan sih).

Akhirnya kupilih cara yang lebih langsung, karena semua akan memiliki dua jawaban pada akhirnya, ya atau tidak. Fortis fortuna adiuvat:

7.1. Menyisipkan ticker di header.php

Di file header.php, tepat setelah pemanggilan:

flow_elated_get_header();

Aku sisipkan:

<?php
if ( ! isset( $_POST['ajaxReq'] ) || $_POST['ajaxReq'] != 'yes' ) {

    flow_elated_get_header();

    if ( function_exists( 'do_shortcode' ) && ( is_front_page() || is_home() ) ) : ?>
        <div class="spotify-header-ticker">
            <?php echo do_shortcode( '[spotify_live_ticker]' ); ?>
        </div>
    <?php
    endif;
}
?>

Jadi ticker hanya muncul di homepage / blog front, dan berada tepat di bawah header Flow.

7.2. Styling supaya match dengan Flow

Awalnya ticker “ngumpet” di balik grid post karena layering dan float (dan makian semakin menjadi di malam itu). Solusinya adalah memainkan clear, z-index, dan gaya bar.

CSS akhir yang kupakai:

/* ==== Spotify ticker di bawah header Flow ==== */
.spotify-header-ticker {
    display: block;
    clear: both;
    position: relative;
    z-index: 9999;
    padding: 12px 0 6px;
    margin: 0 0 8px;
}

/* Bar utama – mengikuti lebar konten Flow */
.spotify-header-ticker .spotify-live-ticker-container {
    margin: 0 auto;
    max-width: 1180px;
    padding: 6px 18px;
    border-radius: 999px;
    background: #ffffff;            /* putih */
    color: #111111;                 /* teks gelap */
    font-size: 13px;
    line-height: 1.4;
    letter-spacing: 0.03em;
    box-shadow: 0 4px 10px rgba(0,0,0,0.06);
    border: 1px solid rgba(0,0,0,0.04);
    display: flex;
    align-items: center;
    overflow: hidden;
}

/* Badge "LIVE" kecil di kiri */
.spotify-header-ticker .spotify-live-ticker-container::before {
    content: "LIVE";
    text-transform: uppercase;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-right: 8px;
    padding: 2px 8px;
    border-radius: 999px;
    background: #27b6a4;     /* warna aksen */
    color: #ffffff;
    flex-shrink: 0;
}

/* Teks yang jalan */
#spotify-live-ticker.spotify-live-ticker {
    flex: 1 1 auto;
    overflow: hidden;
}

.spotify-live-ticker-text {
    white-space: nowrap;
    display: inline-block;
    padding-left: 0;
    animation: slt-marquee 26s linear infinite;  /* dipelanin sedikit */
    color: inherit;
}

/* Sedikit tweak untuk mobile */
@media (max-width: 767px) {
    .spotify-header-ticker {
        padding-top: 8px;
    }

    .spotify-header-ticker .spotify-live-ticker-container {
        max-width: 100%;
        border-radius: 0;
    }

    .spotify-live-ticker-text {
        animation-duration: 32s;
        font-size: 12px;
    }
}

Dengan ini, ticker-nya:

  • Nempel rapi di bawah menu,
  • Lebarnya mengikuti konteks theme,
  • Punya badge “LIVE” kecil sebagai aksen,
  • Dan tetap terbaca di desktop maupun mobile.

Sekarang setiap kali aku membuka homepage, aku disambut oleh bar kecil di bawah menu:

🎧 Now playing : artistjudul lagu dari album ∗album∗

Pelajaran? Aku tahu di balik bar mungil itu ada cerita soal dua VPS, beberapa error HTTP, dan lumayan banyak curl -v di malam hari. Bahwa terkadang kita harus mengambil langkah ekstrim ketika hasil kesabaran kita tidak mendapat jawaban. Mungkin jawaban yang dihasilkan dari tindakanmu tidak sesuai dengan apa yang kamu mau. Tapi, kamu telah menjadi sedikit lebih berani dalam mengambil keputusan.

Feel free untuk mengambil kode ini dan modifikasi sesuai kebutuhan kalian. Ini hanya proyek hobi yang selesai pada tengah malam. Jadi jangan harap akan ada support. You’re on your own, like I did. Dan aku hanya punya sedikit waktu untuk tidur. Esok ku kan tegar kembali