161 lines
5.1 KiB
JavaScript
161 lines
5.1 KiB
JavaScript
const BASE_PATH = '/src/scripts/jassub/';
|
|
|
|
class SubtitleRenderer {
|
|
// 1. Aceptamos 'canvas' en el constructor
|
|
constructor(video, canvas) {
|
|
this.video = video;
|
|
this.canvas = canvas;
|
|
this.instance = null;
|
|
this.currentUrl = null;
|
|
}
|
|
|
|
async init(subtitleUrl) {
|
|
if (!this.video || !this.canvas) return; // 2. Verificamos canvas
|
|
|
|
this.dispose();
|
|
|
|
const finalUrl = subtitleUrl.includes('/api/proxy')
|
|
? subtitleUrl
|
|
: `/api/proxy?url=${encodeURIComponent(subtitleUrl)}`;
|
|
|
|
this.currentUrl = finalUrl;
|
|
|
|
try {
|
|
this.instance = new JASSUB({
|
|
video: this.video,
|
|
canvas: this.canvas,
|
|
subUrl: finalUrl,
|
|
|
|
workerUrl: `${BASE_PATH}jassub-worker.js`,
|
|
wasmUrl: `${BASE_PATH}jassub-worker.wasm`,
|
|
modernWasmUrl: `${BASE_PATH}jassub-worker-modern.wasm`,
|
|
|
|
blendMode: 'js',
|
|
asyncRender: true,
|
|
onDemand: true,
|
|
targetFps: 60,
|
|
debug: false
|
|
});
|
|
|
|
console.log('JASSUB initialized for:', finalUrl);
|
|
|
|
} catch (e) {
|
|
console.error("JASSUB Init Error:", e);
|
|
}
|
|
}
|
|
|
|
resize() {
|
|
if (this.instance && this.instance.resize) {
|
|
this.instance.resize();
|
|
}
|
|
}
|
|
|
|
setTrack(url) {
|
|
const finalUrl = url.includes('/api/proxy')
|
|
? url
|
|
: `/api/proxy?url=${encodeURIComponent(url)}`;
|
|
|
|
if (this.instance) {
|
|
this.instance.setTrackByUrl(finalUrl);
|
|
this.currentUrl = finalUrl;
|
|
} else {
|
|
this.init(url);
|
|
}
|
|
}
|
|
|
|
dispose() {
|
|
if (this.instance) {
|
|
try {
|
|
this.instance.destroy();
|
|
} catch (e) {
|
|
console.warn("Error destroying JASSUB:", e);
|
|
}
|
|
this.instance = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Simple Renderer remains unchanged for SRT/VTT (Non-ASS)
|
|
class SimpleSubtitleRenderer {
|
|
constructor(video, canvas) {
|
|
this.video = video;
|
|
this.canvas = canvas;
|
|
this.ctx = canvas.getContext('2d');
|
|
this.cues = [];
|
|
this.destroyed = false;
|
|
|
|
this.setupCanvas();
|
|
this.video.addEventListener('timeupdate', () => this.render());
|
|
}
|
|
|
|
setupCanvas() {
|
|
const updateSize = () => {
|
|
if (!this.video || !this.canvas) return;
|
|
const rect = this.video.getBoundingClientRect();
|
|
this.canvas.width = rect.width;
|
|
this.canvas.height = rect.height;
|
|
};
|
|
updateSize();
|
|
window.addEventListener('resize', updateSize);
|
|
this.resizeHandler = updateSize;
|
|
}
|
|
|
|
async loadSubtitles(url) {
|
|
try {
|
|
const response = await fetch(`/api/proxy?url=${encodeURIComponent(url)}`);
|
|
const text = await response.text();
|
|
this.cues = this.parseSRT(text);
|
|
} catch (error) {
|
|
console.error('Failed to load subtitles:', error);
|
|
}
|
|
}
|
|
|
|
parseSRT(srtText) {
|
|
const blocks = srtText.trim().split('\n\n');
|
|
return blocks.map(block => {
|
|
const lines = block.split('\n');
|
|
if (lines.length < 3) return null;
|
|
const timeMatch = lines[1].match(/(\d{2}):(\d{2}):(\d{2}),(\d{3}) --> (\d{2}):(\d{2}):(\d{2}),(\d{3})/);
|
|
if (!timeMatch) return null;
|
|
const start = parseInt(timeMatch[1]) * 3600 + parseInt(timeMatch[2]) * 60 + parseInt(timeMatch[3]) + parseInt(timeMatch[4]) / 1000;
|
|
const end = parseInt(timeMatch[5]) * 3600 + parseInt(timeMatch[6]) * 60 + parseInt(timeMatch[7]) + parseInt(timeMatch[8]) / 1000;
|
|
const text = lines.slice(2).join('\n');
|
|
return { start, end, text };
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
render() {
|
|
if (this.destroyed) return;
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
const currentTime = this.video.currentTime;
|
|
const cue = this.cues.find(c => currentTime >= c.start && currentTime <= c.end);
|
|
if (cue) this.drawSubtitle(cue.text);
|
|
}
|
|
|
|
drawSubtitle(text) {
|
|
const lines = text.split('\n');
|
|
const fontSize = Math.max(20, this.canvas.height * 0.04);
|
|
this.ctx.font = `bold ${fontSize}px Arial, sans-serif`;
|
|
this.ctx.textAlign = 'center';
|
|
this.ctx.textBaseline = 'bottom';
|
|
const lineHeight = fontSize * 1.2;
|
|
const startY = this.canvas.height - 60;
|
|
lines.reverse().forEach((line, index) => {
|
|
const y = startY - (index * lineHeight);
|
|
this.ctx.strokeStyle = 'black';
|
|
this.ctx.lineWidth = 4;
|
|
this.ctx.strokeText(line, this.canvas.width / 2, y);
|
|
this.ctx.fillStyle = 'white';
|
|
this.ctx.fillText(line, this.canvas.width / 2, y);
|
|
});
|
|
}
|
|
|
|
dispose() {
|
|
this.destroyed = true;
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
if (this.resizeHandler) window.removeEventListener('resize', this.resizeHandler);
|
|
}
|
|
}
|
|
|
|
window.SubtitleRenderer = SubtitleRenderer;
|
|
window.SimpleSubtitleRenderer = SimpleSubtitleRenderer; |