BC: keyboard shortcuts

for Bandcamp and its embeded player: seek, autoseek when changing track, play/pause, go to prev/next release. Designed for those digging through lots of tunes.

目前為 2022-10-16 提交的版本,檢視 最新版本

// ==UserScript==
// @name         BC: keyboard shortcuts
// @description  for Bandcamp and its embeded player: seek, autoseek when changing track, play/pause, go to prev/next release. Designed for those digging through lots of tunes.
// @namespace    userscript1
// @grant        none
// @version      1.3.5
// @match        https://bandcamp.com/*
// @match        https://*.bandcamp.com/*
// @match        https://*/*
 // @license     GPLv3
// ==/UserScript==

(function() {
  'use strict';

  // Key_ settings refer to physical positions as if you had a QWERTY layout, not letters.
  // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
  const playPause   = 'KeyP';
  const prev        = 'KeyI';
  const next        = 'KeyO';
  const prevAndSeek = 'KeyH';
  const nextAndSeek = 'KeyL';
  const seekBack    = 'KeyJ';
  const seekForward = 'KeyK';
  const prevRelease = 'KeyY';
  const nextRelease = 'KeyU';
  const initialSeek = 60;
  const manualSeek  = 30;
  // end configuration

  
  // only run on *bandcamp.com or bandcamp on a custom domain
  if (!document.location.hostname.endsWith('bandcamp.com')
      && !document.head.querySelector('meta[property="twitter:site"][content="@bandcamp"]') ) {
        return;
  }


  const aud = document.querySelectorAll('audio')[0];
  if (!aud) { return; }
  var prevButton, nextButton, playButton;
  $('#customHeaderWrapper')?.insertAdjacentHTML('beforeEnd', `<div id="shortcuts-message" style="position:absolute; text-align: center; left: 0; right: 0;"></div>`);


  window.addEventListener('keydown', (evt) => {
      if ($('.ui-widget-overlay')) {
        // dialog box is open
        return;
      }

      // check every time to allow collection page to work
      if (!findButtons() ) {
        return;
      }

      // console.log(evt.code);  // uncomment to check key codes
      switch(evt.code) {
          case prev:
              prevButton.click();
              scrollEmbedPlayer();
              break;
          case prevAndSeek:
              prevButton.click();
              aud.currentTime = initialSeek;
              scrollEmbedPlayer();
              break;
          case next:
              nextButton.click();
              scrollEmbedPlayer();
              break;
          case nextAndSeek:
              nextButton.click();
              aud.currentTime = initialSeek;
              scrollEmbedPlayer();
              break;
          case seekBack:
              if (aud.paused) {
                playButton.click();
              }
              aud.currentTime -= manualSeek;
              break;
          case seekForward:
              if (aud.paused) {
                playButton.click();
              }
              aud.currentTime += manualSeek;
              break;
          case playPause:
              playButton.click();
              if (playPause === 'Space') {
                evt.preventDefault();  // prevent page scroll
              }
              break;
          case nextRelease:
              changeRelease(1);
              break;
          case prevRelease:
              changeRelease(-1);
              break;
      }
  }, false);

  function findButtons() {
    prevButton = $('div.prevbutton') || $('div.prev-icon');
    nextButton = $('div.nextbutton') || $('div.next-icon');
    playButton = $('div.playbutton') || $('div.playpause') || $('div#big_play_button');
    return playButton;
  }

  function scrollEmbedPlayer() {
    $('li.currenttrack')?.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'nearest'});
  }

  function $(s) {
    return document.querySelector(s);
  }

  async function changeRelease(direction) {
    if (!/\/album|track/.test(window.location.pathname)) { return; }

    const lsKey = 'shortcuts-release-data';
    if (lsGet(lsKey)) {
      var releaseLinks = lsGet(lsKey);
    } else {
      console.log('fetching release data');
      const r = await fetch('https://' + window.location.hostname + '/music');
      const html = await r.text();
      var parser = new DOMParser();
      var doc = parser.parseFromString(html, 'text/html');
      var releaseLinks = Array.from(doc.querySelectorAll('li.music-grid-item a')).map(a => a.href);
      // cache the list to speed up subsequent use
      lsSet(lsKey, releaseLinks, 1000 * 60 * 10);
    }

    var i = releaseLinks.findIndex(e => e.endsWith(window.location.pathname));
    if (i == -1) {
      console.log('error: current item not found on /music page');
      return;
    }

    var url = releaseLinks[i + direction];
    if (url) {
      // $('#shortcuts-message').textContent = `going to ${(i + 1) + direction} of ${releaseLinks.length}`;
      console.log('navigating to:', url);
      window.location = url;
    } else {
      $('#shortcuts-message').textContent = `no more releases`;
    }
  }

  function lsSet(key, value, ttl) {
    const now = new Date();
    const item = {
      value: value,
      expiry: now.getTime() + ttl,
    };
    localStorage.setItem(key, JSON.stringify(item));
  }

  function lsGet(key) {
    const itemStr = localStorage.getItem(key);
    if (!itemStr) { return null; }
    const item = JSON.parse(itemStr);
    const now = new Date();
    if (now.getTime() > item.expiry) {
      localStorage.removeItem(key);
      return null;
    }
    return item.value;
  }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址