Files
Teleprompter/script.js
ElmGates 172f978edf upload
2025-12-27 13:07:25 +08:00

1010 lines
36 KiB
JavaScript
Raw Permalink 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.

class RemoteController {
constructor(teleprompter) {
this.teleprompter = teleprompter;
this.peer = null;
this.conn = null;
this.roomId = null;
this.isHost = false;
this.isProcessingRemote = false;
this.initializeElements();
this.bindEvents();
}
initializeElements() {
this.createRoomBtn = document.getElementById('createRoomBtn');
this.joinRoomBtn = document.getElementById('joinRoomBtn');
this.joinRoomInput = document.getElementById('joinRoomInput');
this.roomInfo = document.getElementById('roomInfo');
this.roomIdDisplay = document.getElementById('roomIdDisplay');
this.copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
this.connectionStatus = document.getElementById('connectionStatus');
}
bindEvents() {
if(this.createRoomBtn) this.createRoomBtn.addEventListener('click', () => this.createRoom());
if(this.joinRoomBtn) this.joinRoomBtn.addEventListener('click', () => {
const roomId = this.joinRoomInput.value.trim();
if (roomId) this.joinRoom(roomId);
});
if(this.copyRoomIdBtn) this.copyRoomIdBtn.addEventListener('click', () => {
if (this.roomId) {
navigator.clipboard.writeText(this.roomId);
alert('房间 ID 已复制');
}
});
}
generateShortId() {
return Math.floor(1000 + Math.random() * 9000).toString();
}
createRoom() {
const id = this.generateShortId();
this.peer = new Peer(id);
this.peer.on('open', (id) => {
this.roomId = id;
this.isHost = true;
this.showRoomInfo(id);
this.updateStatus('等待连接...');
this.createRoomBtn.disabled = true;
this.joinRoomBtn.disabled = true;
});
this.peer.on('connection', (conn) => {
this.handleConnection(conn);
});
this.peer.on('error', (err) => {
console.error(err);
if (err.type === 'unavailable-id') {
this.peer.destroy();
this.createRoom();
} else {
alert('创建房间失败: ' + err.type);
}
});
}
joinRoom(roomId) {
this.peer = new Peer();
this.peer.on('open', () => {
const conn = this.peer.connect(roomId);
this.handleConnection(conn);
});
this.peer.on('error', (err) => {
console.error(err);
alert('连接失败: ' + err.type);
});
}
handleConnection(conn) {
this.conn = conn;
conn.on('open', () => {
this.updateStatus('已连接');
if (this.isHost) {
this.syncAllState();
}
});
conn.on('data', (data) => {
this.handleData(data);
});
conn.on('close', () => {
this.updateStatus('连接断开');
this.conn = null;
});
}
sendData(type, payload) {
if (this.conn && this.conn.open) {
this.conn.send({ type, payload });
}
}
handleData(data) {
this.isProcessingRemote = true;
try {
switch(data.type) {
case 'fullSync':
this.teleprompter.textInput.value = data.payload.text;
this.teleprompter.formatText(); // 简单显示
// 应用设置
const s = data.payload.settings;
this.teleprompter.scrollSpeed.value = s.scrollSpeed;
this.teleprompter.fontSize.value = s.fontSize;
this.teleprompter.lineHeight.value = s.lineHeight;
this.teleprompter.updateFontSize();
this.teleprompter.updateLineHeight();
if (data.payload.currentLine !== undefined) {
setTimeout(() => {
this.teleprompter.jumpToLineByIndex(data.payload.currentLine);
}, 100);
}
break;
case 'control':
if (data.payload.action === 'start') this.teleprompter.startScrolling();
if (data.payload.action === 'pause') this.teleprompter.pauseScrolling();
if (data.payload.action === 'reset') this.teleprompter.resetScrolling();
if (data.payload.action === 'prevLine') this.teleprompter.prevLine();
if (data.payload.action === 'nextLine') this.teleprompter.nextLine();
break;
case 'text':
this.teleprompter.textInput.value = data.payload.text;
this.teleprompter.formatText();
break;
case 'jump':
this.teleprompter.jumpToLineByIndex(data.payload.index);
break;
}
} finally {
this.isProcessingRemote = false;
}
}
showRoomInfo(id) {
if(this.roomInfo) this.roomInfo.style.display = 'block';
if(this.roomIdDisplay) this.roomIdDisplay.textContent = id;
}
updateStatus(msg) {
if(this.connectionStatus) this.connectionStatus.textContent = '状态: ' + msg;
}
syncAllState() {
this.sendData('fullSync', {
text: this.teleprompter.textInput.value,
settings: {
fontSize: this.teleprompter.fontSize.value,
scrollSpeed: this.teleprompter.scrollSpeed.value,
lineHeight: this.teleprompter.lineHeight.value
}
});
}
}
class Teleprompter {
constructor() {
console.log('开始初始化提词器...');
this.isScrolling = false;
this.isPaused = false;
this.scrollInterval = null;
this.currentPosition = 0;
this.formattedText = [];
this.currentLine = 0;
this.totalLines = 0;
this.lineHeights = []; // 存储每行高度
this.initializeElements();
this.bindEvents();
this.loadSettings();
this.initializeTextPosition();
this.checkMobileDevice();
console.log('提词器初始化完成');
this.remoteController = new RemoteController(this);
}
initializeElements() {
console.log('正在初始化元素...');
// 文本输入
this.textInput = document.getElementById('textInput');
this.formatTextBtn = document.getElementById('formatText');
// 滚动控制
this.scrollSpeed = document.getElementById('scrollSpeed');
this.speedValue = document.getElementById('speedValue');
this.startScrollBtn = document.getElementById('startScroll');
this.pauseScrollBtn = document.getElementById('pauseScroll');
this.resetScrollBtn = document.getElementById('resetScroll');
this.prevLineBtn = document.getElementById('prevLineBtn');
this.nextLineBtn = document.getElementById('nextLineBtn');
// 显示设置
this.fontSize = document.getElementById('fontSize');
this.fontSizeValue = document.getElementById('fontSizeValue');
this.lineHeight = document.getElementById('lineHeight');
this.lineHeightValue = document.getElementById('lineHeightValue');
this.textColor = document.getElementById('textColor');
this.bgColor = document.getElementById('bgColor');
this.mirrorText = document.getElementById('mirrorText');
// 高级功能
this.advancedToggle = document.getElementById('advancedToggle');
this.advancedContent = document.getElementById('advancedContent');
this.maxWordsPerParagraph = document.getElementById('maxWordsPerParagraph');
this.maxWordsValue = document.getElementById('maxWordsValue');
this.forceParagraphBreak = document.getElementById('forceParagraphBreak');
this.formatTextBtn = document.getElementById('formatTextBtn');
this.undoFormatBtn = document.getElementById('undoFormatBtn');
this.mainFormatBtn = document.getElementById('formatText'); // 主显示按钮
// 显示区域
this.textDisplay = document.getElementById('textDisplay');
this.teleprompterContent = document.getElementById('teleprompterContent');
this.readingGuide = document.getElementById('readingGuide');
// 控制面板
this.controlPanel = document.getElementById('controlPanel');
this.togglePanelBtn = document.getElementById('togglePanel');
this.toggleFullscreenBtn = document.getElementById('toggleFullscreen');
this.showPanelBtn = document.getElementById('showPanelBtn');
// 进度显示
this.progressFill = document.getElementById('progressFill');
this.currentLineSpan = document.getElementById('currentLine');
this.totalLinesSpan = document.getElementById('totalLines');
this.jumpToLineInput = document.getElementById('jumpToLine');
this.jumpButton = document.getElementById('jumpButton');
// 容器
this.container = document.querySelector('.container');
this.teleprompterContainer = document.getElementById('teleprompterContainer');
// 手机端提示
this.mobileAlert = document.getElementById('mobileAlert');
this.closeMobileAlertBtn = document.getElementById('closeMobileAlert');
console.log('元素初始化完成');
}
bindEvents() {
// 文本格式化
this.formatTextBtn.addEventListener('click', () => this.smartFormatText()); // 高级功能中的格式化按钮
this.undoFormatBtn.addEventListener('click', () => this.undoFormat());
this.mainFormatBtn.addEventListener('click', () => this.formatText()); // 主显示按钮(简单显示)
// 滚动控制
this.scrollSpeed.addEventListener('input', () => {
this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1);
this.saveSettings();
});
this.startScrollBtn.addEventListener('click', () => this.startScrolling());
this.pauseScrollBtn.addEventListener('click', () => this.pauseScrolling());
this.resetScrollBtn.addEventListener('click', () => this.resetScrolling());
if(this.prevLineBtn) this.prevLineBtn.addEventListener('click', () => this.prevLine());
if(this.nextLineBtn) this.nextLineBtn.addEventListener('click', () => this.nextLine());
// 显示设置
this.fontSize.addEventListener('input', () => this.updateFontSize());
this.lineHeight.addEventListener('input', () => this.updateLineHeight());
this.textColor.addEventListener('input', () => this.updateTextColor());
this.bgColor.addEventListener('input', () => this.updateBgColor());
this.mirrorText.addEventListener('change', () => this.updateMirrorText());
// 高级功能
this.advancedToggle.addEventListener('click', () => this.toggleAdvancedMenu());
this.maxWordsPerParagraph.addEventListener('input', () => this.updateMaxWordsPerParagraph());
this.forceParagraphBreak.addEventListener('change', () => this.updateForceParagraphBreak());
// 全屏和面板控制
this.toggleFullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
this.togglePanelBtn.addEventListener('click', () => this.togglePanel());
this.showPanelBtn.addEventListener('click', () => this.togglePanel());
// 快速定位
this.jumpButton.addEventListener('click', () => this.jumpToLine());
this.jumpToLineInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.jumpToLine();
});
// 键盘快捷键
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
// 窗口大小变化
window.addEventListener('resize', () => this.handleResize());
// 全屏状态变化
document.addEventListener('fullscreenchange', () => this.handleFullscreenChange());
// 手机端提示
if (this.closeMobileAlertBtn) {
this.closeMobileAlertBtn.addEventListener('click', () => {
this.mobileAlert.style.display = 'none';
});
}
}
initializeTextPosition() {
// 确保文本显示区域有正确的初始位置
setTimeout(() => {
if (this.formattedText.length === 0) {
// 如果没有格式化文本,显示默认提示并定位到中间位置
const containerHeight = this.teleprompterContent.clientHeight;
const lineHeight = this.getCurrentLineHeight();
const startPosition = (containerHeight / 2) - lineHeight;
this.textDisplay.style.transform = `translateY(${startPosition}px)`;
}
}, 100);
}
checkMobileDevice() {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768;
console.log('设备检测:', {
userAgent: navigator.userAgent,
width: window.innerWidth,
isMobile: isMobile
});
if (isMobile) {
// 检查是否已经显示过提示
if (!sessionStorage.getItem('mobileAlertShown')) {
this.mobileAlert.style.display = 'flex';
sessionStorage.setItem('mobileAlertShown', 'true');
}
}
}
// 显示文本功能(简单显示,不格式化)
formatText() {
const text = this.textInput.value.trim();
if (!text) {
alert('请先输入文本内容!');
return;
}
console.log('开始显示文本,输入长度:', text.length);
// 简单按换行符分段,不进行智能格式化
const lines = text.split('\n').map(line => line.trim()).filter(line => line !== '');
console.log('简单分段结果:', lines);
this.formattedText = lines;
this.totalLines = lines.length;
this.currentLine = 0;
this.currentPosition = 0;
this.lineHeights = []; // 重置行高数组
// 更新跳转输入框的最大值
this.jumpToLineInput.max = this.totalLines;
this.jumpToLineInput.value = 1;
// 显示文本
this.displayFormattedText();
this.updateProgress();
// 保存到本地存储
localStorage.setItem('teleprompterText', text);
this.notifyRemote('text', { text: text });
// alert('文本显示完成!共' + lines.length + '行');
}
// 智能格式化功能
smartFormatText() {
const text = this.textInput.value.trim();
if (!text) {
alert('请先输入文本内容!');
return;
}
console.log('开始智能格式化文本,输入长度:', text.length);
// 进行智能格式化
const sentences = this.splitByPunctuation(text);
console.log('智能格式化结果:', sentences);
this.formattedText = sentences;
this.totalLines = sentences.length;
this.currentLine = 0;
this.currentPosition = 0;
this.lineHeights = []; // 重置行高数组
// 更新跳转输入框的最大值
this.jumpToLineInput.max = this.totalLines;
this.jumpToLineInput.value = 1;
// 显示格式化后的文本
this.displayFormattedText();
this.updateProgress();
// 保存到本地存储
localStorage.setItem('teleprompterText', text);
// alert('智能格式化完成!共' + sentences.length + '行');
}
// 撤销格式化功能
undoFormat() {
const text = this.textInput.value.trim();
if (!text) {
alert('请先输入文本内容!');
return;
}
console.log('开始撤销格式化,恢复原始段落');
// 简单按换行符分段,恢复原始段落结构
const lines = text.split('\n').map(line => line.trim()).filter(line => line !== '');
console.log('撤销格式化结果:', lines);
this.formattedText = lines;
this.totalLines = lines.length;
this.currentLine = 0;
this.currentPosition = 0;
this.lineHeights = []; // 重置行高数组
// 更新跳转输入框的最大值
this.jumpToLineInput.max = this.totalLines;
this.jumpToLineInput.value = 1;
// 显示格式化后的文本
this.displayFormattedText();
this.updateProgress();
// 保存到本地存储
localStorage.setItem('teleprompterText', text);
// alert('格式化已撤销!共' + lines.length + '行');
}
splitByPunctuation(text) {
// 获取最大段落字数设置
const maxWords = parseInt(this.maxWordsPerParagraph.value) || 50;
const forceBreak = this.forceParagraphBreak.checked;
// 定义中文和英文的标点符号
const punctuation = /[。!?;:.!?;:\n]/;
const sentences = [];
let current = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
current += char;
// 如果遇到标点符号,或者文本长度超过限制
if (punctuation.test(char) || current.length > maxWords) {
// 查找下一个非空白字符
let j = i + 1;
while (j < text.length && /\s/.test(text[j])) {
j++;
}
// 如果当前句子长度合理,或者已经累积了较多字符
if (current.trim().length > 10 || sentences.length === 0) {
sentences.push(current.trim());
current = '';
i = j - 1; // 跳过空白字符
}
}
}
// 处理剩余的文本
if (current.trim()) {
// 如果剩余文本较长且启用了强制分段,进一步分割
if (forceBreak && current.length > maxWords * 1.5) {
const remainingSentences = this.forceSplitLongText(current, maxWords);
sentences.push(...remainingSentences);
} else {
sentences.push(current.trim());
}
}
return sentences;
}
forceSplitLongText(text, maxWords) {
const sentences = [];
let current = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
current += char;
// 强制在最大字数处分割,优先在标点符号处
if (current.length >= maxWords) {
// 向前查找最近的标点符号
let breakPoint = current.length;
for (let j = current.length - 1; j >= Math.max(0, current.length - 20); j--) {
if (/[,。!?;:,\.!?;:]/.test(current[j])) {
breakPoint = j + 1;
break;
}
}
const segment = current.substring(0, breakPoint).trim();
if (segment.length > 10) {
sentences.push(segment);
}
current = current.substring(breakPoint);
}
}
if (current.trim()) {
sentences.push(current.trim());
}
return sentences;
}
displayFormattedText() {
let html = '';
this.lineHeights = []; // 重置行高数组
this.formattedText.forEach((sentence, index) => {
html += `<p data-line="${index}">${sentence}</p>`;
});
if (html === '') {
html = '<p>请输入您的提词内容,然后点击"格式化文本"按钮开始使用...</p>';
}
this.textDisplay.innerHTML = html;
this.totalLinesSpan.textContent = this.totalLines;
// 应用当前的行间距设置
const currentLineHeight = this.lineHeight.value;
const paragraphs = this.textDisplay.querySelectorAll('p');
paragraphs.forEach(p => {
p.style.lineHeight = currentLineHeight;
});
// 计算每行的高度并存储
paragraphs.forEach((p, index) => {
const height = p.offsetHeight + parseFloat(getComputedStyle(p).marginBottom);
this.lineHeights[index] = height;
});
// 计算初始位置,将第一行文本对齐到中间位置
const containerHeight = this.teleprompterContent.clientHeight;
const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight();
const totalContentHeight = this.lineHeights.reduce((sum, height) => sum + height, 0);
// 如果内容高度小于容器高度,将第一行放在中间位置
// 如果内容高度大于容器高度,确保第一行不会被遮挡
let startPosition = (containerHeight / 2) - firstLineHeight;
// 确保起始位置不会导致文本显示在容器底部之外
if (startPosition < 0) {
startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距
}
// 重置滚动位置
this.currentPosition = 0;
const isMirror = this.mirrorText.checked;
const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)';
this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`;
}
// 滚动功能
startScrolling() {
this.notifyRemote('control', { action: 'start' });
if (this.formattedText.length === 0) {
alert('请先格式化文本!');
return;
}
if (this.isPaused) {
this.resumeScrolling();
return;
}
this.isScrolling = true;
this.isPaused = false;
this.startScrollBtn.textContent = '滚动中...';
this.startScrollBtn.disabled = true;
this.readingGuide.classList.add('visible');
const speed = parseFloat(this.scrollSpeed.value);
const interval = Math.max(20, 210 - speed); // 扩大速度转换范围最低20ms最高190ms
this.scrollInterval = setInterval(() => {
this.scrollStep();
}, interval);
}
scrollStep() {
if (this.formattedText.length === 0) {
this.stopScrolling();
alert('请先格式化文本!');
return;
}
const lineHeight = this.getCurrentLineHeight();
this.currentPosition += 1;
// 更新当前行
let accumulatedHeight = 0;
let newLine = 0;
for (let i = 0; i < this.lineHeights.length; i++) {
accumulatedHeight += this.lineHeights[i];
if (accumulatedHeight > this.currentPosition) {
newLine = i;
break;
}
newLine = i;
}
if (newLine !== this.currentLine && newLine < this.totalLines) {
this.currentLine = newLine;
this.updateProgress();
}
// 应用滚动
const isMirror = this.mirrorText.checked;
const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)';
this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`;
// 检查是否滚动到底部
const containerHeight = this.teleprompterContent.clientHeight;
const contentHeight = this.textDisplay.scrollHeight;
if (this.currentPosition + containerHeight >= contentHeight) {
this.stopScrolling();
alert('已滚动到文本末尾!');
}
}
pauseScrolling() {
if (this.isScrolling && !this.isPaused) {
this.notifyRemote('control', { action: 'pause' });
this.isPaused = true;
clearInterval(this.scrollInterval);
this.pauseScrollBtn.textContent = '继续';
this.startScrollBtn.textContent = '继续';
this.startScrollBtn.disabled = false;
}
}
resumeScrolling() {
if (this.isPaused) {
this.isPaused = false;
this.startScrolling();
this.pauseScrollBtn.textContent = '暂停';
}
}
resetScrolling() {
this.stopScrolling();
this.currentPosition = 0;
this.currentLine = 0;
// 重置到初始中间位置
const containerHeight = this.teleprompterContent.clientHeight;
const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight();
let startPosition = (containerHeight / 2) - firstLineHeight;
// 确保起始位置不会导致文本显示在容器底部之外
if (startPosition < 0) {
startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距
}
const isMirror = this.mirrorText.checked;
const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)';
this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`;
this.updateProgress();
this.readingGuide.classList.remove('visible');
}
stopScrolling() {
this.isScrolling = false;
this.isPaused = false;
clearInterval(this.scrollInterval);
this.startScrollBtn.textContent = '开始';
this.startScrollBtn.disabled = false;
this.pauseScrollBtn.textContent = '暂停';
}
// 显示设置
updateFontSize() {
const size = this.fontSize.value;
this.fontSizeValue.textContent = size + 'px';
this.textDisplay.style.fontSize = size + 'px';
// 重新计算行高
if (this.formattedText.length > 0) {
setTimeout(() => this.displayFormattedText(), 50);
}
this.saveSettings();
}
updateLineHeight() {
const height = this.lineHeight.value;
this.lineHeightValue.textContent = height;
// 应用到所有段落,让每一行都有这个行间距
const paragraphs = this.textDisplay.querySelectorAll('p');
paragraphs.forEach(p => {
p.style.lineHeight = height;
});
// 重新计算行高
if (this.formattedText.length > 0) {
setTimeout(() => this.displayFormattedText(), 50);
}
this.saveSettings();
}
updateTextColor() {
const color = this.textColor.value;
this.textDisplay.style.color = color;
this.saveSettings();
}
updateBgColor() {
const color = this.bgColor.value;
this.teleprompterContainer.style.backgroundColor = color;
this.saveSettings();
}
updateMirrorText() {
const text = this.textInput.value.trim();
if (text === '') {
this.textDisplay.innerHTML = '';
this.formattedText = [];
return;
}
// 简单按换行符分段,保持原始段落结构
const lines = text.split('\n').map(line => line.trim()).filter(line => line !== '');
this.formattedText = lines;
this.totalLines = lines.length;
const formattedText = lines.map(line => `<p>${line}</p>`).join('');
this.textDisplay.innerHTML = formattedText;
// 应用当前设置
this.updateFontSize();
this.updateLineHeight();
this.updateTextColor();
this.updateBgColor();
// 更新镜像状态
const isMirror = this.mirrorText.checked;
if (isMirror) {
this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(-1)`;
} else {
this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(1)`;
}
this.saveSettings();
}
// 高级功能
toggleAdvancedMenu() {
const isVisible = this.advancedContent.style.display === 'block';
this.advancedContent.style.display = isVisible ? 'none' : 'block';
this.advancedToggle.style.transform = isVisible ? 'rotate(0deg)' : 'rotate(180deg)';
}
updateMaxWordsPerParagraph() {
const value = this.maxWordsPerParagraph.value;
this.maxWordsValue.textContent = value;
this.saveSettings();
}
updateForceParagraphBreak() {
this.saveSettings();
}
// 全屏功能
toggleFullscreen() {
if (!document.fullscreenElement) {
this.container.requestFullscreen().then(() => {
this.container.classList.add('fullscreen');
this.toggleFullscreenBtn.textContent = '退出全屏';
}).catch(err => {
alert(`无法进入全屏模式: ${err.message}`);
});
} else {
document.exitFullscreen().then(() => {
this.container.classList.remove('fullscreen');
this.toggleFullscreenBtn.textContent = '全屏显示';
});
}
}
togglePanel() {
this.controlPanel.classList.toggle('hidden');
const isHidden = this.controlPanel.classList.contains('hidden');
this.togglePanelBtn.textContent = isHidden ? '显示控制面板' : '隐藏控制面板';
this.showPanelBtn.style.display = isHidden ? 'block' : 'none';
// 同时切换容器的panel-hidden类用于扩展文本显示区域
this.container.classList.toggle('panel-hidden', isHidden);
// 切换body的背景样式隐藏蓝色渐变背景
document.body.classList.toggle('panel-hidden-bg', isHidden);
}
// 键盘快捷键
handleKeyboard(e) {
if (e.target.tagName === 'TEXTAREA') return;
switch(e.key) {
case ' ':
e.preventDefault();
if (this.isScrolling && !this.isPaused) {
this.pauseScrolling();
} else {
this.startScrolling();
}
break;
case 'r':
case 'R':
this.resetScrolling();
break;
case 'f':
case 'F':
this.toggleFullscreen();
break;
case 'Escape':
if (document.fullscreenElement) {
this.toggleFullscreen();
}
break;
}
}
// 快速定位功能
jumpToLine() {
if (!this.formattedText || this.formattedText.length === 0) {
alert('请先格式化文本');
return;
}
const targetLine = parseInt(this.jumpToLineInput.value);
if (isNaN(targetLine) || targetLine < 1 || targetLine > this.formattedText.length) {
alert(`请输入有效的行号 (1-${this.formattedText.length})`);
return;
}
this.jumpToLineByIndex(targetLine - 1);
this.notifyRemote('jump', { index: targetLine - 1 });
}
notifyRemote(type, payload) {
if (this.remoteController && !this.remoteController.isProcessingRemote) {
this.remoteController.sendData(type, payload);
}
}
prevLine() {
if (!this.formattedText || this.formattedText.length === 0) return;
this.jumpToLineByIndex(this.currentLine - 1);
this.notifyRemote('control', { action: 'prevLine' });
}
nextLine() {
if (!this.formattedText || this.formattedText.length === 0) return;
this.jumpToLineByIndex(this.currentLine + 1);
this.notifyRemote('control', { action: 'nextLine' });
}
jumpToLineByIndex(index) {
if (index < 0) index = 0;
if (index >= this.totalLines) index = this.totalLines - 1;
this.stopScrolling();
this.currentLine = index;
this.currentPosition = this.calculatePositionForLine(this.currentLine);
const isMirror = this.mirrorText.checked;
const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)';
this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`;
this.updateProgress();
}
calculatePositionForLine(lineIndex) {
let position = 0;
for (let i = 0; i < lineIndex && i < this.lineHeights.length; i++) {
position += this.lineHeights[i] || this.getCurrentLineHeight();
}
return position;
}
// 进度更新
updateProgress() {
const progress = this.totalLines > 0 ? (this.currentLine / this.totalLines) * 100 : 0;
this.progressFill.style.width = progress + '%';
this.currentLineSpan.textContent = this.currentLine + 1;
}
// 工具方法
getCurrentLineHeight() {
const firstParagraph = this.textDisplay.querySelector('p');
if (firstParagraph) {
return firstParagraph.offsetHeight +
parseFloat(getComputedStyle(firstParagraph).marginBottom);
}
return 50; // 默认值
}
handleResize() {
// 重新计算行高和位置
if (this.formattedText.length > 0) {
this.displayFormattedText();
} else {
// 如果没有格式化文本,重新初始化位置
this.initializeTextPosition();
}
}
handleFullscreenChange() {
if (!document.fullscreenElement) {
this.container.classList.remove('fullscreen');
this.toggleFullscreenBtn.textContent = '全屏显示';
}
}
// 设置保存和加载
saveSettings() {
const settings = {
scrollSpeed: this.scrollSpeed.value,
fontSize: this.fontSize.value,
lineHeight: this.lineHeight.value,
textColor: this.textColor.value,
bgColor: this.bgColor.value,
mirrorText: this.mirrorText.checked,
maxWordsPerParagraph: this.maxWordsPerParagraph.value,
forceParagraphBreak: this.forceParagraphBreak.checked
};
localStorage.setItem('teleprompterSettings', JSON.stringify(settings));
}
loadSettings() {
// 加载设置
const settings = localStorage.getItem('teleprompterSettings');
if (settings) {
const parsed = JSON.parse(settings);
this.scrollSpeed.value = parsed.scrollSpeed || 50;
this.fontSize.value = parsed.fontSize || 32;
this.lineHeight.value = parsed.lineHeight || 1.8;
this.textColor.value = parsed.textColor || '#ffffff';
this.bgColor.value = parsed.bgColor || '#000000';
this.mirrorText.checked = parsed.mirrorText || false;
this.maxWordsPerParagraph.value = parsed.maxWordsPerParagraph || 50;
this.forceParagraphBreak.checked = parsed.forceParagraphBreak !== undefined ? parsed.forceParagraphBreak : true;
// 应用设置
this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1);
this.updateFontSize();
this.updateLineHeight();
this.updateTextColor();
this.updateBgColor();
this.updateMirrorText();
this.updateMaxWordsPerParagraph();
this.updateForceParagraphBreak();
}
// 加载文本
const savedText = localStorage.getItem('teleprompterText');
if (savedText) {
this.textInput.value = savedText;
setTimeout(() => this.formatText(), 100);
}
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
new Teleprompter();
});
// 添加一些提示信息
window.addEventListener('load', () => {
setTimeout(() => {
console.log('提词器应用已加载完成!');
console.log('快捷键说明:');
console.log('- 空格键:开始/暂停滚动');
console.log('- R键重置滚动');
console.log('- F键切换全屏');
console.log('- ESC键退出全屏');
}, 1000);
});