Files
ytplayer/frontend/index.html
Jonathan Sykes bac6665b3d Add Windows support via a Tauri (WebView2) shell
zero-native has no Windows target yet, so add a parallel Tauri shell
for Windows that reuses the same frontend:

- src-tauri/: Rust commands (yt_search, yt_streams, store_load,
  store_save) mirroring the Zig bridge; spawns bundled yt-dlp.exe with
  CREATE_NO_WINDOW; data stored in app_data_dir
- frontend bridge now auto-detects window.__TAURI__ vs window.zero and
  routes accordingly; CSP updated for Tauri IPC
- tauri.conf.json bundles yt-dlp.exe as a resource and builds nsis/msi
- scripts/make-icon.js generates appicon.png for 'tauri icon'
- README: native Windows build steps + WSLg fallback note
2026-06-14 10:04:27 +08:00

148 lines
5.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'self' https: data: asset: http://asset.localhost; media-src 'self' https: blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; script-src 'self'; connect-src 'self' https: ipc: http://ipc.localhost;"
/>
<title>YT Player</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar">
<div class="brand">
<span class="brand-mark"></span> YT Player
</div>
<nav class="nav">
<button class="nav-item active" data-view="search">🔍 Search</button>
<button class="nav-item" data-view="history">🕘 History</button>
</nav>
<div class="pl-header">
<span>Playlists</span>
<button id="newPlaylistBtn" class="icon-btn" title="New playlist"></button>
</div>
<div id="playlistList" class="playlist-list"></div>
<div class="sidebar-footer">
<label class="switch">
<input type="checkbox" id="audioOnlyToggle" />
<span>Audio-only mode</span>
</label>
</div>
</aside>
<!-- Main -->
<main class="main">
<header class="topbar">
<form id="searchForm" class="search-form">
<input
id="searchInput"
type="text"
placeholder="Search YouTube… (no login, no ads)"
autocomplete="off"
/>
<button type="submit" class="search-btn">Search</button>
</form>
</header>
<div class="body">
<!-- Player pane -->
<section id="playerPane" class="player-pane empty">
<div class="player-stage">
<video id="video" playsinline></video>
<audio id="audio"></audio>
<div id="artFallback" class="art-fallback hidden">
<img id="artImg" alt="" />
</div>
<div id="playerPlaceholder" class="player-placeholder">
<div class="ph-logo"></div>
<p>Search and pick a video to start watching — ad-free.</p>
</div>
<div id="bufferSpinner" class="spinner hidden"></div>
</div>
<div id="controls" class="controls hidden">
<div class="seek-row">
<span id="curTime" class="time">0:00</span>
<input id="seek" class="seek" type="range" min="0" max="1000" value="0" />
<span id="durTime" class="time">0:00</span>
</div>
<div class="btn-row">
<button id="prevBtn" class="ctrl" title="Previous"></button>
<button id="playBtn" class="ctrl play" title="Play/Pause"></button>
<button id="nextBtn" class="ctrl" title="Next"></button>
<div class="vol">
<button id="muteBtn" class="ctrl" title="Mute">🔊</button>
<input id="volume" type="range" min="0" max="1" step="0.01" value="1" />
</div>
<div class="spacer"></div>
<label class="sel">
Speed
<select id="speedSelect">
<option value="0.5">0.5×</option>
<option value="0.75">0.75×</option>
<option value="1" selected>1×</option>
<option value="1.25">1.25×</option>
<option value="1.5">1.5×</option>
<option value="2">2×</option>
</select>
</label>
<label class="sel">
Quality
<select id="qualitySelect"></select>
</label>
<button id="fsBtn" class="ctrl" title="Fullscreen"></button>
</div>
</div>
<div id="nowPlayingMeta" class="now-meta hidden">
<div class="np-title" id="npTitle"></div>
<div class="np-channel" id="npChannel"></div>
</div>
</section>
<!-- List pane -->
<section class="list-pane">
<div class="list-header">
<h2 id="listTitle">Search</h2>
<div id="listActions" class="list-actions"></div>
</div>
<div id="status" class="status hidden"></div>
<div id="cards" class="cards"></div>
</section>
</div>
</main>
</div>
<!-- Generic modal -->
<div id="modal" class="modal-backdrop hidden">
<div class="modal">
<h3 id="modalTitle"></h3>
<div id="modalBody"></div>
<div class="modal-actions" id="modalActions"></div>
</div>
</div>
<div id="toast" class="toast hidden"></div>
<script src="app.js"></script>
</body>
</html>