Skip to content

integrate remote sound cloud plugin #15

@dbee01

Description

@dbee01

Iframe embed

<iframe
  title="SoundCloud playlist player"
  width="100%"
  height="450"
  loading="lazy"
  scrolling="no"
  frameborder="no"
  allow="autoplay"
  src="https://w.soundcloud.com/player/?url=ENCODED_PLAYLIST_URL&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false">
</iframe>

Single page code (html + css + js) simple-player.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Simple SoundCloud Player</title>
  <style>
    body{ font-family:system-ui,Arial; padding:18px; background:#fafafa; }
    .player{ max-width:720px; margin:0 auto; background:#fff; padding:12px; border-radius:8px; box-shadow:0 6px 24px rgba(0,0,0,0.06); }
    .controls{ display:flex; gap:8px; margin-top:10px; align-items:center; flex-wrap:wrap; }
    button{ background:#ff5500; color:#fff; border:none; padding:8px 12px; border-radius:6px; cursor:pointer; }
    input[type="url"]{ flex:1; padding:8px; border-radius:6px; border:1px solid #ddd; }
  </style>
</head>
<body>
  <div class="player">
    <h2>Simple SoundCloud Player</h2>
    <iframe id="sc" width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay"
      src="https://w.soundcloud.com/player/?url=https%3A//soundcloud.com/forss/flickermood&auto_play=false"></iframe>

    <div class="controls">
      <button id="play">Play</button>
      <button id="pause">Pause</button>
      <input id="url" type="url" placeholder="SoundCloud track/playlist URL">
      <button id="load">Load & Play</button>
    </div>
  </div>

  <script src="https://w.soundcloud.com/player/api.js"></script>
  <script>
    var widget = SC.Widget(document.getElementById('sc'));
    document.getElementById('play').addEventListener('click', function(){ widget.play(); });
    document.getElementById('pause').addEventListener('click', function(){ widget.pause(); });
    document.getElementById('load').addEventListener('click', function(){
      var url = document.getElementById('url').value.trim();
      if (!url) return alert('Enter a SoundCloud URL');
      widget.load(url, { auto_play: true });
    });
  </script>
</body>
</html>

control soundcloud using js - player.js

// player.js — accessible SoundCloud widget + playlist with localStorage persistence
// Requires https://w.soundcloud.com/player/api.js to be loaded first.

(function () {
  // Storage keys
  var STORAGE_KEY = 'sc_playlist_v1';
  var STORAGE_SEL_KEY = 'sc_playlist_sel_v1';

  // Utils
  function msToTime(ms) {
    if (ms == null) return '00:00';
    var total = Math.floor(ms / 1000);
    var s = total % 60;
    var m = Math.floor(total / 60);
    return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
  }

  // Safe JSON helpers
  function saveJSON(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (e) {
      console.warn('Failed to save to localStorage', e);
    }
  }
  function loadJSON(key) {
    try {
      var raw = localStorage.getItem(key);
      if (!raw) return null;
      return JSON.parse(raw);
    } catch (e) {
      console.warn('Failed to parse localStorage item', key, e);
      return null;
    }
  }

  // Elements
  var iframe = document.getElementById('sc-widget');
  if (!iframe) return;
  var widget = SC.Widget(iframe);

  var btnPlay = document.getElementById('btn-play');
  var btnPause = document.getElementById('btn-pause');
  var btnToggle = document.getElementById('btn-toggle');
  var btnSeekBack = document.getElementById('btn-seek-back');
  var btnSeekFwd = document.getElementById('btn-seek-fwd');
  var volume = document.getElementById('volume');
  var position = document.getElementById('position');
  var btnLoad = document.getElementById('btn-load');
  var trackUrlInput = document.getElementById('track-url');
  var trackTitle = document.getElementById('track-title');

  var playlistEl = document.getElementById('playlist');
  var addUrlInput = document.getElementById('new-track-url');
  var btnAdd = document.getElementById('btn-add');
  var btnClear = document.getElementById('btn-clear');

  // Sample playlist (used only if no saved playlist exists)
  var defaultPlaylist = [
    { url: 'https://soundcloud.com/forss/flickermood', label: 'Forss — Flickermood' }
  ];

  // Load playlist from storage or fallback to default
  var stored = loadJSON(STORAGE_KEY);
  var playlist = Array.isArray(stored) && stored.length ? stored : defaultPlaylist.slice();

  // Persist selected index (so selection survives reload)
  var storedSel = loadJSON(STORAGE_SEL_KEY);
  var selectedIndex = (typeof storedSel === 'number' && storedSel >= 0 && storedSel < playlist.length) ? storedSel : 0;

  // Render playlist
  function renderPlaylist() {
    playlistEl.innerHTML = '';
    playlist.forEach(function (item, idx) {
      var li = document.createElement('li');
      li.className = 'playlist-item';
      li.setAttribute('role', 'option');
      li.setAttribute('tabindex', '0');
      li.setAttribute('data-url', item.url);
      li.setAttribute('data-index', String(idx));
      li.setAttribute('aria-selected', (idx === selectedIndex) ? 'true' : 'false');

      li.innerHTML = '<span class="label">' + escapeHtml(item.label || item.url) + '</span>' +
                     '<div style="display:flex;gap:6px">' +
                       '<button class="btn-load-inline" title="Load this track" aria-label="Load ' + escapeForAria(item.label || item.url) + '">Load</button>' +
                       '<button class="btn-remove-inline" title="Remove from playlist" aria-label="Remove ' + escapeForAria(item.label || item.url) + '">Remove</button>' +
                     '</div>';

      // Click to load or remove
      li.addEventListener('click', function (e) {
        if (e.target && e.target.classList.contains('btn-load-inline')) {
          loadTrack(item.url);
        } else if (e.target && e.target.classList.contains('btn-remove-inline')) {
          removePlaylistItem(idx);
        } else {
          setFocusedPlaylistItem(idx);
          loadTrack(item.url);
        }
      });

      // Keyboard interaction
      li.addEventListener('keydown', function (e) {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          loadTrack(item.url);
        } else if (e.key === 'ArrowDown') {
          e.preventDefault();
          focusNextPlaylistItem(idx);
        } else if (e.key === 'ArrowUp') {
          e.preventDefault();
          focusPrevPlaylistItem(idx);
        } else if (e.key === 'Home') {
          e.preventDefault();
          focusPlaylistItem(0);
        } else if (e.key === 'End') {
          e.preventDefault();
          focusPlaylistItem(playlist.length - 1);
        }
      });

      playlistEl.appendChild(li);
    });
    // Ensure selectedIndex is valid
    if (playlist.length === 0) selectedIndex = -1;
    else if (selectedIndex >= playlist.length) selectedIndex = playlist.length - 1;

    // Persist playlist and selection
    savePlaylist();
    saveSelectedIndex();
  }

  function escapeHtml(s){ return (s+'').replace(/[&<>"']/g, function(m){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]; }); }
  function escapeForAria(s){ return (s+'').replace(/"/g, "'"); }

  function setFocusedPlaylistItem(index) {
    var items = playlistEl.querySelectorAll('.playlist-item');
    items.forEach(function (el) {
      var i = parseInt(el.getAttribute('data-index'), 10);
      el.setAttribute('aria-selected', String(i === index));
    });
    focusPlaylistItem(index);
    selectedIndex = index;
    saveSelectedIndex();
  }

  function focusPlaylistItem(index) {
    var items = playlistEl.querySelectorAll('.playlist-item');
    var el = items[index];
    if (el) el.focus();
  }

  function focusNextPlaylistItem(currentIndex) {
    var next = Math.min(playlist.length - 1, currentIndex + 1);
    focusPlaylistItem(next);
    selectedIndex = next;
    saveSelectedIndex();
  }

  function focusPrevPlaylistItem(currentIndex) {
    var prev = Math.max(0, currentIndex - 1);
    focusPlaylistItem(prev);
    selectedIndex = prev;
    saveSelectedIndex();
  }

  // Save playlist and selected index
  function savePlaylist() {
    // Only store url + label to avoid extra data
    var toStore = playlist.map(function (p) { return { url: p.url, label: p.label || p.url }; });
    saveJSON(STORAGE_KEY, toStore);
  }
  function saveSelectedIndex() {
    if (typeof selectedIndex === 'number' && selectedIndex >= 0) saveJSON(STORAGE_SEL_KEY, selectedIndex);
    else localStorage.removeItem(STORAGE_SEL_KEY);
  }

  // Remove an item
  function removePlaylistItem(index) {
    if (index < 0 || index >= playlist.length) return;
    playlist.splice(index, 1);
    // adjust selectedIndex if needed
    if (selectedIndex === index) selectedIndex = -1;
    else if (selectedIndex > index) selectedIndex--;
    renderPlaylist();
  }

  // Load track into widget
  function loadTrack(url, opts) {
    if (!url) return;
    widget.load(url, Object.assign({ auto_play: true, show_artwork: true }, opts || {}));
    // Mark selected playlist item if present
    var idx = playlist.findIndex(function (p) { return p.url === url; });
    if (idx !== -1) {
      setFocusedPlaylistItem(idx);
    } else {
      // not found: clear selection
      selectedIndex = -1;
      saveSelectedIndex();
      // optionally add to playlist? current behavior: do not auto-add
    }
  }

  // Widget ready setup
  widget.bind(SC.Widget.Events.READY, function () {
    // set initial volume
    widget.setVolume(parseInt(volume.value, 10));

    // update meta
    widget.getCurrentSound(function (sound) {
      if (sound && sound.title) trackTitle.textContent = 'Title: ' + sound.title;
    });

    // position updater
    setInterval(function () {
      widget.getPosition(function (ms) {
        widget.getDuration(function (dur) {
          position.textContent = msToTime(ms) + ' / ' + msToTime(dur);
        });
      });
    }, 500);
  });

  // Playback controls
  btnPlay.addEventListener('click', function () { widget.play(); });
  btnPause.addEventListener('click', function () { widget.pause(); });
  btnToggle.addEventListener('click', function () {
    widget.isPaused(function (paused) {
      if (paused) {
        widget.play();
        btnToggle.setAttribute('aria-pressed', 'true');
      } else {
        widget.pause();
        btnToggle.setAttribute('aria-pressed', 'false');
      }
    });
  });

  btnSeekBack.addEventListener('click', function () {
    widget.getPosition(function (ms) {
      var newPos = Math.max(0, ms - 10000);
      widget.seekTo(newPos);
    });
  });
  btnSeekFwd.addEventListener('click', function () {
    widget.getPosition(function (ms) {
      widget.seekTo(ms + 10000);
    });
  });

  volume.addEventListener('input', function () {
    var v = parseInt(this.value, 10);
    widget.setVolume(v);
  });

  btnLoad.addEventListener('click', function () {
    var url = trackUrlInput.value.trim();
    if (!url) {
      alert('Enter a SoundCloud track or playlist URL.');
      return;
    }
    loadTrack(url);
    // optional: trackUrlInput.value = '';
  });

  // Update meta on PLAY
  widget.bind(SC.Widget.Events.PLAY, function () {
    widget.getCurrentSound(function (sound) {
      if (sound && sound.title) trackTitle.textContent = 'Title: ' + sound.title;
    });
    btnToggle.setAttribute('aria-pressed', 'true');
  });

  widget.bind(SC.Widget.Events.PAUSE, function () {
    btnToggle.setAttribute('aria-pressed', 'false');
  });

  widget.bind(SC.Widget.Events.FINISH, function () {
    // optional: advance playlist if loaded from playlist
    var selIndex = playlist.findIndex(function (p) { return p.url === currentLoadedUrl; });
    if (selIndex !== -1 && selIndex < playlist.length - 1) {
      loadTrack(playlist[selIndex + 1].url);
    }
  });

  // Errors
  widget.bind(SC.Widget.Events.ERROR, function (e) {
    console.warn('SoundCloud widget error', e);
    alert('SoundCloud Widget error. The track may be restricted and not embeddable.');
  });

  // Track currently loaded (best-effort)
  var currentLoadedUrl = null;
  widget.bind(SC.Widget.Events.READY, function () {
    widget.getCurrentSound(function (sound) {
      if (sound && sound.permalink_url) currentLoadedUrl = sound.permalink_url;
    });
  });
  widget.bind(SC.Widget.Events.PLAY, function () {
    widget.getCurrentSound(function (sound) {
      if (sound && sound.permalink_url) currentLoadedUrl = sound.permalink_url;
    });
  });

  // Playlist management UI
  btnAdd.addEventListener('click', function () {
    var url = addUrlInput.value.trim();
    if (!url) return;
    playlist.push({ url: url, label: url });
    renderPlaylist();
    addUrlInput.value = '';
  });

  btnClear.addEventListener('click', function () {
    if (!confirm('Clear all playlist items?')) return;
    playlist = [];
    selectedIndex = -1;
    renderPlaylist();
  });

  // Global keyboard shortcuts while focus is inside the widget container
  var widgetContainer = document.getElementById('widget-container');
  widgetContainer.addEventListener('keydown', function (e) {
    // Avoid interfering with typing in inputs
    var tag = (e.target && e.target.tagName) || '';
    if (tag === 'INPUT' || tag === 'TEXTAREA') return;

    if (e.key === ' ') {
      // toggle play/pause
      e.preventDefault();
      btnToggle.click();
    } else if (e.key === 'ArrowLeft') {
      e.preventDefault();
      btnSeekBack.click();
    } else if (e.key === 'ArrowRight') {
      e.preventDefault();
      btnSeekFwd.click();
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      // increase volume
      var v = Math.min(100, parseInt(volume.value, 10) + 5);
      volume.value = v;
      volume.dispatchEvent(new Event('input'));
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      var v2 = Math.max(0, parseInt(volume.value, 10) - 5);
      volume.value = v2;
      volume.dispatchEvent(new Event('input'));
    }
  });

  // Allow arrow navigation on playlist wrapper (when not focusing items)
  playlistEl.addEventListener('keydown', function (e) {
    var items = Array.from(playlistEl.querySelectorAll('.playlist-item'));
    var focused = document.activeElement;
    var idx = items.indexOf(focused);
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (idx === -1) { focusPlaylistItem(0); } else focusNextPlaylistItem(idx);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (idx === -1) { focusPlaylistItem(playlist.length - 1); } else focusPrevPlaylistItem(idx);
    } else if (e.key === 'Home') {
      e.preventDefault();
      focusPlaylistItem(0);
    } else if (e.key === 'End') {
      e.preventDefault();
      focusPlaylistItem(playlist.length - 1);
    }
  });

  // Initialize
  renderPlaylist();

  // Focus selection if present
  if (selectedIndex >= 0 && playlist.length > 0) setFocusedPlaylistItem(selectedIndex);

  // Helper: safe focus if playlist empty
  // Minimal protection: avoid throwing if widget isn't available
})();

simple-player.js

// simple-player.js
// - Initializes SC.Widget
// - Adds basic controls (play/pause/load)
// - Integrates with Twilio call events to pause/resume playback
//
// Usage:
// 1) Include <script src="https://w.soundcloud.com/player/api.js"></script> in your page.
// 2) Include Twilio SDK if you use it (optional).
// 3) Include this script after the DOM or at end of body.
// 4) Ensure your iframe has id="sc" (same as simple-player.html example).

(function () {
  // Utility: safe DOM ready
  function ready(fn) {
    if (document.readyState !== 'loading') return fn();
    document.addEventListener('DOMContentLoaded', fn);
  }

  ready(function () {
    var iframe = document.getElementById('sc');
    if (!iframe) {
      console.warn('SoundCloud iframe #sc not found.');
      return;
    }

    // Initialize widget
    var widget = null;
    try {
      widget = SC && SC.Widget && SC.Widget(iframe);
    } catch (e) {
      console.error('SC.Widget init failed', e);
      return;
    }

    // Wire simple UI actions if present (matches simple-player.html)
    var btnPlay = document.getElementById('play');
    var btnPause = document.getElementById('pause');
    var btnLoad = document.getElementById('load');
    var urlInput = document.getElementById('url');

    if (btnPlay) btnPlay.addEventListener('click', function () { widget.play(); });
    if (btnPause) btnPause.addEventListener('click', function () { widget.pause(); });
    if (btnLoad && urlInput) btnLoad.addEventListener('click', function () {
      var u = urlInput.value.trim();
      if (!u) return alert('Enter a SoundCloud URL');
      widget.load(u, { auto_play: true });
    });

    // Twilio integration: pause/resume logic
    var wasPlayingBeforeCall = false;
    var resumeButton = null;

    function ensureResumeUI() {
      if (resumeButton) return;
      resumeButton = document.createElement('button');
      resumeButton.textContent = 'Resume audio';
      Object.assign(resumeButton.style, {
        position: 'fixed',
        right: '12px',
        bottom: '12px',
        zIndex: 99999,
        background: '#ff5500',
        color: '#fff',
        border: 'none',
        padding: '10px 14px',
        borderRadius: '6px',
        cursor: 'pointer'
      });
      resumeButton.addEventListener('click', function () {
        widget.play();
        removeResumeUI();
      });
      document.body.appendChild(resumeButton);
    }
    function removeResumeUI() {
      if (!resumeButton) return;
      resumeButton.remove();
      resumeButton = null;
    }

    function pauseIfPlaying() {
      try {
        widget.isPaused(function (paused) {
          if (!paused) {
            wasPlayingBeforeCall = true;
            try { widget.pause(); } catch (e) { console.warn('widget.pause failed', e); }
          } else {
            wasPlayingBeforeCall = false;
          }
        });
      } catch (e) {
        console.warn('pauseIfPlaying error', e);
      }
    }

    function resumeIfNeeded() {
      if (!wasPlayingBeforeCall) return;
      wasPlayingBeforeCall = false; // clear the remembered state (we will try)
      try {
        widget.play();
        // If browser blocks autoplay, widget may still be paused -> show resume UI
        setTimeout(function () {
          try {
            widget.isPaused(function (paused) {
              if (paused) {
                ensureResumeUI();
              } else {
                removeResumeUI();
              }
            });
          } catch (e) {
            ensureResumeUI();
          }
        }, 300);
      } catch (err) {
        console.warn('widget.play failed', err);
        ensureResumeUI();
      }
    }

    // Attach to Twilio Device events if present. Support multiple global shapes.
    function attachTwilioHandlers(obj) {
      if (!obj) return false;
      try {
        // Most Twilio Device implementations emit 'connect' / 'disconnect' / 'incoming' / 'error'
        if (typeof obj.on === 'function') {
          obj.on('connect', pauseIfPlaying);
          obj.on('incoming', pauseIfPlaying);
          obj.on('disconnect', resumeIfNeeded);
          obj.on('cancel', resumeIfNeeded);
          obj.on('error', resumeIfNeeded);
          console.log('Attached Twilio handlers via .on');
          return true;
        }
        // Some uses expose addEventListener-like API
        if (typeof obj.addEventListener === 'function') {
          obj.addEventListener('connect', pauseIfPlaying);
          obj.addEventListener('incoming', pauseIfPlaying);
          obj.addEventListener('disconnect', resumeIfNeeded);
          obj.addEventListener('error', resumeIfNeeded);
          console.log('Attached Twilio handlers via addEventListener');
          return true;
        }
      } catch (e) {
        console.warn('Failed to attach Twilio handlers', e);
      }
      return false;
    }

    // Try immediate attachment (common shapes)
    var attached = false;
    attached = attachTwilioHandlers(window.Device) || attached;
    attached = attachTwilioHandlers(window.Twilio && window.Twilio.Device) || attached;
    attached = attachTwilioHandlers(window.Twilio) || attached; // sometimes Twilio.Device is nested

    // If Twilio not available yet, poll briefly and attach when present
    if (!attached) {
      var tries = 0;
      var maxTries = 20; // ~10 seconds at 500ms interval
      var poll = setInterval(function () {
        tries++;
        attached = attachTwilioHandlers(window.Device) || attached;
        attached = attachTwilioHandlers(window.Twilio && window.Twilio.Device) || attached;
        attached = attachTwilioHandlers(window.Twilio) || attached;
        if (attached || tries >= maxTries) {
          clearInterval(poll);
          if (!attached) {
            console.info('Twilio Device not detected; Twilio integration not attached. If you use a different SDK object, call pauseIfPlaying/resumeIfNeeded from your call lifecycle callbacks.');
          }
        }
      }, 500);
    } else {
      console.info('Twilio integration attached.');
    }

    // Expose helpers so other code can call them directly if needed
    window.__sc_pauseForCall = pauseIfPlaying;
    window.__sc_resumeAfterCall = resumeIfNeeded;

    // Optional: when page is unloaded or hidden, remove resume UI
    window.addEventListener('pagehide', removeResumeUI);
    document.addEventListener('visibilitychange', function () {
      if (document.visibilityState === 'hidden') removeResumeUI();
    });
  });
})();

style.css

:root{
  --max-width: 980px;
  --accent: #ff5500;
  --muted: #666;
  --gap: 12px;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
  color-scheme: light;
}

*{box-sizing:border-box}
body{
  margin:0;
  padding:24px;
  background:#fafafa;
  color:#111;
  display:flex;
  justify-content:center;
}

.player-page{
  width: min(96vw, var(--max-width));
}

h1,h2{ margin:8px 0; }

.sc-iframe-wrapper{
  background:#fff;
  border-radius:6px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  overflow:hidden;
}

.widget-container{
  margin-top:12px;
}

.controls{
  margin-top:10px;
  display:flex;
  flex-direction:column;
  gap: var(--gap);
}

.row{
  display:flex;
  gap:8px;
  align-items:center;
  flex-wrap:wrap;
}

button{
  background:var(--accent);
  color:#fff;
  border:none;
  padding:8px 12px;
  border-radius:6px;
  cursor:pointer;
}
button.secondary{
  background:#e6e6e6;
  color:#111;
}
button:disabled{ opacity:0.6; cursor:default; }

input[type="range"]{
  width:160px;
}
input[type="url"]{
  flex:1 1 320px;
  padding:8px;
  border-radius:6px;
  border:1px solid #ddd;
}

.note{
  margin-top:8px;
  color:var(--muted);
  font-size:0.95rem;
}

.meta-row{ margin-top:4px; }

.playlist-section{
  margin-top:20px;
}

#playlist{
  margin:8px 0;
  padding:0;
  list-style:none;
  display:flex;
  flex-direction:column;
  gap:6px;
  max-height:320px;
  overflow:auto;
  border:1px solid #eee;
  padding:8px;
  border-radius:6px;
  background:#fff;
}

.playlist-item{
  display:flex;
  justify-content:space-between;
  gap:12px;
  align-items:center;
  padding:8px;
  border-radius:6px;
  cursor:pointer;
  outline: none;
}
.playlist-item[aria-selected="true"]{
  background: rgba(255,85,0,0.08);
  border:1px solid rgba(255,85,0,0.14);
}
.playlist-item:focus{
  box-shadow: 0 0 0 3px rgba(255,85,0,0.12);
}

.hints small{ color:var(--muted); }

@media (max-width:560px){
  .controls { gap:8px; }
  input[type="range"]{ width:100px; }
}

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>SoundCloud Player (index)</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main class="player-page" id="main" role="main">
    <h1>SoundCloud Player</h1>

    <!-- Player / Widget -->
    <section class="widget-section" aria-labelledby="widget-heading" role="region" aria-label="SoundCloud player">
      <h2 id="widget-heading">Player</h2>

      <div class="widget-container" id="widget-container">
        <div class="sc-iframe-wrapper" aria-hidden="false">
          <!-- Replace the `url=` value with your SoundCloud track/playlist URL (URL-encoded) or leave and load via JS -->
          <iframe id="sc-widget"
                  title="SoundCloud Player"
                  width="100%"
                  height="166"
                  scrolling="no"
                  frameborder="no"
                  allow="autoplay"
                  src="https://w.soundcloud.com/player/?url=https%3A//soundcloud.com/forss/flickermood&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false">
          </iframe>
        </div>

        <div class="controls" role="group" aria-label="Player controls">
          <div class="row">
            <button id="btn-play" aria-label="Play" title="Play">Play</button>
            <button id="btn-pause" aria-label="Pause" title="Pause">Pause</button>
            <button id="btn-toggle" aria-pressed="false" aria-label="Toggle play/pause" title="Toggle">Toggle</button>
            <button id="btn-seek-back" aria-label="Seek back 10 seconds" title="-10s">-10s</button>
            <button id="btn-seek-fwd" aria-label="Seek forward 10 seconds" title="+10s">+10s</button>
          </div>

          <div class="row">
            <label for="volume">Volume</label>
            <input id="volume" type="range" min="0" max="100" value="80" aria-label="Volume" />
            <span id="position" aria-live="polite">00:00 / 00:00</span>
          </div>

          <div class="row load-row">
            <input id="track-url" type="url" placeholder="Paste SoundCloud track/playlist URL" aria-label="Track URL" />
            <button id="btn-load" aria-label="Load track" title="Load & Play">Load & Play</button>
          </div>

          <div class="row meta-row" aria-live="polite">
            <strong id="track-title">Title: —</strong>
          </div>

          <div class="row hints">
            <small>Keyboard: while focus is inside player — Space toggles play, ←/→ seek, ↑/↓ volume, Enter loads playlist item.</small>
          </div>
        </div>
      </div>
    </section>

    <!-- Playlist -->
    <section class="playlist-section" aria-labelledby="playlist-heading" role="region" aria-label="Playlist">
      <h2 id="playlist-heading">Playlist</h2>
      <p class="note">Use arrow keys to navigate the playlist; press Enter to load the focused track.</p>

      <ul id="playlist" role="listbox" tabindex="0" aria-label="SoundCloud playlist">
        <!-- Items populated by player.js -->
      </ul>

      <div class="row playlist-actions">
        <input id="new-track-url" type="url" placeholder="Add track URL to playlist" aria-label="New track URL" />
        <button id="btn-add" aria-label="Add to playlist">Add to Playlist</button>
        <button id="btn-clear" aria-label="Clear playlist">Clear Playlist</button>
      </div>
    </section>
  </main>

  <!-- SoundCloud widget API (required) -->
  <script src="https://w.soundcloud.com/player/api.js"></script>
  <!-- Your player logic (the player.js you already have) -->
  <script src="player.js"></script>
</body>
</html>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions