Something went wrong. Try again.
experiments of a tiny cytube-like player with yt-dlp
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213import { For } from "solid-js";import type { TrackState, QueueSummary, HistoryEntry, PlaylistEntry } from "./types";
interface QueueItemProps { title: string; duration: string; thumbnail: string | null; pending?: boolean; extraClass?: string; failed?: string | null;}
function QueueItem(props: QueueItemProps) { const failed = props.failed; return ( <div class={`queue-item${props.extraClass ? " " + props.extraClass : ""}${failed ? " queue-item-failed" : ""}`} title={failed ?? undefined} > {props.thumbnail && ( <img class="queue-thumb" src={props.thumbnail} alt="" onError={(e) => (e.currentTarget.hidden = true)} /> )} <span class="queue-title"> {props.pending ? "\u23F3 " : ""} {props.title} </span> <span class="queue-duration">{props.duration}</span> {failed && ( <span class="queue-item-failed-reason" aria-live="polite"> {failed} </span> )} </div> );}
interface QueueProps { currentTrack: TrackState | null; queue: QueueSummary[]; history: HistoryEntry[]; playlist: PlaylistEntry[]; autoplayEnabled: boolean; skipThreshold: number; onAddToPlaylist?: (url: string) => void; onRemoveFromPlaylist?: (id: string) => void; onSetAutoplay?: (enabled: boolean) => void; onSetSkipThreshold?: (percent: number) => void; onMoveQueueItem?: (from: number, to: number) => void; onShuffleQueue?: () => void; onRemoveQueueItem?: (itemId: string) => void;}
export function Queue(props: QueueProps) { return ( <div class="queue-panel"> <div class="queue-section"> <h3>Now Playing</h3> {props.currentTrack ? ( <QueueItem title={props.currentTrack.title} duration={props.currentTrack.duration} thumbnail={props.currentTrack.thumbnail} extraClass="current" /> ) : ( <div class="queue-empty">Paste a URL to start watching together</div> )} </div>
<div class="queue-section"> <h3> Up Next ({props.queue.length}) {props.onShuffleQueue && props.queue.length > 1 && ( <button type="button" class="queue-shuffle" title="Shuffle up next" onClick={() => props.onShuffleQueue?.()} > ⇄ Shuffle </button> )} </h3> <For each={props.queue}> {(item, index) => ( <div class="queue-item-row"> <QueueItem title={item.title} duration={item.duration} thumbnail={item.thumbnail} pending={item.pending} /> {props.onMoveQueueItem && ( <div class="queue-item-actions"> <button type="button" class="queue-item-action" title="Move up" disabled={index() === 0} onClick={() => props.onMoveQueueItem?.(index(), index() - 1)} > ▲ </button> <button type="button" class="queue-item-action" title="Move down" disabled={index() === props.queue.length - 1} onClick={() => props.onMoveQueueItem?.(index(), index() + 1)} > ▼ </button> <button type="button" class="queue-item-action queue-item-remove" title="Remove from queue" onClick={() => props.onRemoveQueueItem?.(item.id)} > × </button> </div> )} </div> )} </For> {props.queue.length === 0 && <div class="queue-empty">Tracks you add will appear here</div>} </div>
<div class="queue-section"> <h3>Playlist ({props.playlist.length})</h3> <div class="playlist-controls"> <label class="playlist-toggle"> <input type="checkbox" checked={props.autoplayEnabled} disabled={props.playlist.length === 0} onChange={(e) => props.onSetAutoplay?.(e.currentTarget.checked)} /> <span>Autoplay</span> </label> <label class="playlist-threshold" title="Skip when this percent of the room votes"> <span class="playlist-threshold-label">Skip ≥ {props.skipThreshold}%</span> <input type="range" min="0" max="100" step="1" value={props.skipThreshold} onInput={(e) => props.onSetSkipThreshold?.(parseInt(e.currentTarget.value, 10))} /> </label> </div> <For each={props.playlist}> {(item) => ( <div class="queue-item-row"> <QueueItem title={item.title} duration={item.duration} thumbnail={item.thumbnail} pending={item.pending} failed={item.failed} /> <button type="button" class="queue-item-action queue-item-remove" title="Remove from playlist" onClick={() => props.onRemoveFromPlaylist?.(item.id)} > × </button> </div> )} </For> {props.playlist.length === 0 && ( <div class="queue-empty">Add links to build a perpetual playlist</div> )} </div>
<div class="queue-section"> <h3>Recently Played</h3> <For each={props.history}> {(item) => ( <div class="queue-item-row"> <QueueItem title={item.title} duration={item.duration} thumbnail={item.thumbnail} extraClass="history" /> <button type="button" class="queue-item-action" title="Add to playlist" onClick={() => props.onAddToPlaylist?.(item.url)} > + </button> </div> )} </For> {props.history.length === 0 && ( <div class="queue-empty">Recently played tracks show up here</div> )} </div> </div> );}