Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MediaTok Player</title> | |
| <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.21.0/dist/ort.min.js"></script> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: system-ui, -apple-system, sans-serif; | |
| background: #0f0f1a; color: #e0e0f0; min-height: 100vh; | |
| display: flex; flex-direction: column; align-items: center; | |
| } | |
| header { | |
| padding: 2rem 1rem; text-align: center; | |
| background: linear-gradient(135deg, #1a1a3e, #2d1b4e); | |
| width: 100%; | |
| } | |
| header h1 { font-size: 2rem; font-weight: 700; } | |
| header p { color: #a0a0c0; margin-top: 0.5rem; } | |
| main { padding: 2rem; max-width: 900px; width: 100%; } | |
| .card { | |
| background: #1a1a2e; border-radius: 12px; padding: 1.5rem; | |
| margin-bottom: 1.5rem; border: 1px solid #2a2a4e; | |
| } | |
| .dropzone { | |
| border: 2px dashed #4a4a7e; border-radius: 8px; padding: 2rem; | |
| text-align: center; cursor: pointer; transition: all 0.2s; | |
| } | |
| .dropzone:hover, .dropzone.dragover { border-color: #7c5cbf; background: #1f1f3a; } | |
| .dropzone input[type="file"] { display: none; } | |
| .dropzone p { color: #8080b0; } | |
| .dropzone .icon { font-size: 2.5rem; margin-bottom: 0.5rem; } | |
| #video-container { | |
| display: none; position: relative; width: 100%; | |
| background: #000; border-radius: 8px; overflow: hidden; | |
| } | |
| #video-container canvas { width: 100%; display: block; } | |
| #controls { | |
| display: none; gap: 0.5rem; align-items: center; margin-top: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| #controls button { | |
| background: #3a2a6e; color: #e0e0f0; border: none; | |
| padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; | |
| font-size: 0.9rem; transition: background 0.2s; | |
| } | |
| #controls button:hover { background: #5a3a9e; } | |
| #controls button:disabled { opacity: 0.4; cursor: not-allowed; } | |
| #status { color: #8080b0; margin-top: 1rem; font-size: 0.9rem; } | |
| .progress-bar { width: 100%; height: 4px; background: #2a2a4e; border-radius: 2px; margin-top: 0.5rem; } | |
| .progress-bar .fill { height: 100%; background: #7c5cbf; border-radius: 2px; width: 0%; transition: width 0.2s; } | |
| .info-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.3rem 1rem; font-size: 0.85rem; } | |
| .info-grid dt { color: #8080b0; } | |
| .info-grid dd { color: #e0e0f0; } | |
| #error { color: #ff6b6b; margin-top: 1rem; display: none; } | |
| #fps-display { color: #7c5cbf; font-weight: 600; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>π¬ MediaTok Player</h1> | |
| <p>Neural token video β decoded in your browser with WebGPU</p> | |
| </header> | |
| <main> | |
| <div class="card"> | |
| <h2>Open File</h2> | |
| <div class="dropzone" id="dropzone"> | |
| <div class="icon">π</div> | |
| <p>Drop a <code>.gtkv</code> file here or click to browse</p> | |
| <input type="file" id="file-input" accept=".gtkv"> | |
| </div> | |
| <div class="progress-bar"><div class="fill" id="progress-fill"></div></div> | |
| <div id="status">Ready β load a .gtkv file to begin</div> | |
| <div id="error"></div> | |
| </div> | |
| <div class="card" id="info-card" style="display:none"> | |
| <h2>File Info</h2> | |
| <dl class="info-grid" id="info-grid"></dl> | |
| </div> | |
| <div class="card" id="player-card" style="display:none"> | |
| <h2>Playback</h2> | |
| <div id="video-container"> | |
| <canvas id="video-canvas"></canvas> | |
| </div> | |
| <div id="controls"> | |
| <button id="play-btn">βΆ Play</button> | |
| <button id="stop-btn" disabled>βΉ Stop</button> | |
| <span id="fps-display"></span> | |
| <span id="frame-display" style="color:#8080b0"></span> | |
| </div> | |
| </div> | |
| </main> | |
| <script type="module"> | |
| import { GtkvReader } from './js/gtkv.js'; | |
| import { GigaTokenDecoder } from './js/decoder.js'; | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const status = document.getElementById('status'); | |
| const error = document.getElementById('error'); | |
| const progressFill = document.getElementById('progress-fill'); | |
| const infoCard = document.getElementById('info-card'); | |
| const infoGrid = document.getElementById('info-grid'); | |
| const playerCard = document.getElementById('player-card'); | |
| const canvas = document.getElementById('video-canvas'); | |
| const playBtn = document.getElementById('play-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const fpsDisplay = document.getElementById('fps-display'); | |
| const frameDisplay = document.getElementById('frame-display'); | |
| const videoContainer = document.getElementById('video-container'); | |
| let decoder = null; | |
| let reader = null; | |
| let frames = []; | |
| let playing = false; | |
| let animationId = null; | |
| let currentFrame = 0; | |
| async function initDecoder() { | |
| if (decoder) return; | |
| status.textContent = 'β³ Loading decoder model...'; | |
| try { | |
| decoder = new GigaTokenDecoder(); | |
| await decoder.load(); | |
| status.textContent = 'β Decoder loaded'; | |
| } catch (e) { | |
| status.textContent = 'β Failed to load decoder'; | |
| showError(e); | |
| } | |
| } | |
| async function loadFile(file) { | |
| error.style.display = 'none'; | |
| progressFill.style.width = '0%'; | |
| try { | |
| status.textContent = 'π Reading file...'; | |
| const arrayBuffer = await file.arrayBuffer(); | |
| const data = new Uint8Array(arrayBuffer); | |
| reader = new GtkvReader(data); | |
| const info = reader.header; | |
| infoCard.style.display = 'block'; | |
| infoGrid.innerHTML = Object.entries(info).map(([k, v]) => | |
| `<dt>${k}</dt><dd>${v}</dd>` | |
| ).join(''); | |
| status.textContent = 'π§ Decoding tokens...'; | |
| playerCard.style.display = 'block'; | |
| // Decode all chunks | |
| const allTokens = reader.decodeAllTokens(); | |
| status.textContent = `π¦ ${allTokens.length} tokens decoded`; | |
| // Render first frame | |
| await decodeAndRenderFrame(allTokens, 0); | |
| playBtn.disabled = false; | |
| status.textContent = 'β Ready β press Play'; | |
| } catch (e) { | |
| showError(e); | |
| } | |
| } | |
| async function decodeAndRenderFrame(allTokens, frameIndex) { | |
| if (!decoder) return; | |
| // Get tokens for this frame | |
| const frameTokens = allTokens.slice(frameIndex * 444, (frameIndex + 1) * 444); | |
| // Run ONNX models | |
| const frame = await decoder.decode(frameTokens); | |
| // Render to canvas | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = frame[0].length; | |
| canvas.height = frame.length; | |
| const imageData = ctx.createImageData(canvas.width, canvas.height); | |
| for (let y = 0; y < canvas.height; y++) { | |
| for (let x = 0; x < canvas.width; x++) { | |
| const i = (y * canvas.width + x) * 4; | |
| imageData.data[i] = Math.max(0, Math.min(255, frame[y][x] * 255)); | |
| imageData.data[i+1] = Math.max(0, Math.min(255, frame[y][x+1] * 255)); | |
| imageData.data[i+2] = Math.max(0, Math.min(255, frame[y][x+2] * 255)); | |
| imageData.data[i+3] = 255; | |
| } | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| } | |
| function showError(e) { | |
| error.textContent = `Error: ${e.message || e}`; | |
| error.style.display = 'block'; | |
| console.error(e); | |
| } | |
| // Events | |
| dropzone.addEventListener('click', () => fileInput.click()); | |
| dropzone.addEventListener('dragover', (e) => { e.preventDefault(); dropzone.classList.add('dragover'); }); | |
| dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover')); | |
| dropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropzone.classList.remove('dragover'); | |
| if (e.dataTransfer.files.length) loadFile(e.dataTransfer.files[0]); | |
| }); | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length) loadFile(fileInput.files[0]); | |
| }); | |
| // Init decoder on page load | |
| initDecoder(); | |
| </script> | |
| </body> | |
| </html> | |