mediatok-player / index.html
Daankular's picture
Upload index.html with huggingface_hub
62d71de verified
Raw
History Blame Contribute Delete
7.49 kB
<!DOCTYPE html>
<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>