/**
|
|
* 音频相关工具方法
|
|
* 从 AudioControls.vue 抽离的通用工具方法
|
|
*/
|
|
|
|
/**
|
|
* 智能分割文本,按句号和逗号分割中英文文本
|
|
* @param {string} text - 要分割的文本
|
|
* @returns {Array} 分割后的文本段落数组
|
|
*/
|
|
export function splitTextIntelligently(text) {
|
|
if (!text || typeof text !== 'string') {
|
|
return [text];
|
|
}
|
|
|
|
// 判断是否为中文文本(包含中文字符)
|
|
const isChinese = /[\u4e00-\u9fa5]/.test(text);
|
|
const maxLength = isChinese ? 100 : 200;
|
|
|
|
// 如果文本长度不超过限制,直接返回
|
|
if (text.length <= maxLength) {
|
|
return [{
|
|
text: text,
|
|
startIndex: 0,
|
|
endIndex: text.length - 1
|
|
}];
|
|
}
|
|
|
|
const segments = [];
|
|
let currentText = text;
|
|
let globalStartIndex = 0;
|
|
|
|
while (currentText.length > 0) {
|
|
if (currentText.length <= maxLength) {
|
|
// 剩余文本不超过限制,直接添加
|
|
segments.push({
|
|
text: currentText,
|
|
startIndex: globalStartIndex,
|
|
endIndex: globalStartIndex + currentText.length - 1
|
|
});
|
|
break;
|
|
}
|
|
|
|
// 在限制长度内寻找最佳分割点
|
|
let splitIndex = maxLength;
|
|
let bestSplitIndex = -1;
|
|
|
|
// 优先寻找句号
|
|
for (let i = Math.min(maxLength, currentText.length - 1); i >= Math.max(0, maxLength - 50); i--) {
|
|
const char = currentText[i];
|
|
if (char === '。' || char === '.') {
|
|
bestSplitIndex = i + 1; // 包含句号
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 如果没找到句号,寻找逗号
|
|
if (bestSplitIndex === -1) {
|
|
for (let i = Math.min(maxLength, currentText.length - 1); i >= Math.max(0, maxLength - 50); i--) {
|
|
const char = currentText[i];
|
|
if (char === ',' || char === ',' || char === ';' || char === ';') {
|
|
bestSplitIndex = i + 1; // 包含标点符号
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果还是没找到合适的分割点,使用默认长度
|
|
if (bestSplitIndex === -1) {
|
|
bestSplitIndex = maxLength;
|
|
}
|
|
|
|
// 提取当前段落
|
|
const segment = currentText.substring(0, bestSplitIndex).trim();
|
|
if (segment.length > 0) {
|
|
segments.push({
|
|
text: segment,
|
|
startIndex: globalStartIndex,
|
|
endIndex: globalStartIndex + segment.length - 1
|
|
});
|
|
}
|
|
|
|
// 更新剩余文本和全局索引
|
|
currentText = currentText.substring(bestSplitIndex).trim();
|
|
globalStartIndex += bestSplitIndex;
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
|
|
/**
|
|
* 格式化时间显示
|
|
* @param {number} seconds - 秒数
|
|
* @returns {string} 格式化后的时间字符串 (mm:ss)
|
|
*/
|
|
export function formatTime(seconds) {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* 生成音频缓存键
|
|
* @param {string} courseId - 课程ID
|
|
* @param {number} pageNumber - 页面号码
|
|
* @param {string} voiceId - 音色ID
|
|
* @returns {string} 缓存键
|
|
*/
|
|
export function generateCacheKey(courseId, pageNumber, voiceId) {
|
|
return `${courseId}_${pageNumber}_${voiceId}`;
|
|
}
|
|
|
|
/**
|
|
* 验证缓存数据的有效性
|
|
* @param {Object} cachedData - 缓存的音频数据
|
|
* @param {number} expectedPage - 期望的页面号码
|
|
* @returns {boolean} 缓存数据是否有效
|
|
*/
|
|
export function validateCacheData(cachedData, expectedPage) {
|
|
if (!cachedData || !cachedData.audios || cachedData.audios.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
// 检查页面号码匹配
|
|
if (expectedPage !== null && cachedData.pageNumber && cachedData.pageNumber !== expectedPage) {
|
|
return false;
|
|
}
|
|
|
|
// 检查音频URL有效性
|
|
const firstAudio = cachedData.audios[0];
|
|
if (!firstAudio || !firstAudio.url) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 查找第一个非导语音频的索引
|
|
* @param {Array} audioArray - 音频数组
|
|
* @returns {number} 第一个非导语音频的索引,如果没有找到返回-1
|
|
*/
|
|
export function findFirstNonLeadAudio(audioArray) {
|
|
if (!audioArray || audioArray.length === 0) {
|
|
return -1;
|
|
}
|
|
|
|
// 从第一个音频开始查找非导语音频
|
|
for (let i = 0; i < audioArray.length; i++) {
|
|
const audioData = audioArray[i];
|
|
if (audioData && !audioData.isLead) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// 如果所有音频都是导语,返回 -1 表示不播放
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* 限制缓存大小,保留最近访问的页面
|
|
* @param {Object} audioCache - 音频缓存对象
|
|
* @param {number} maxSize - 最大缓存数量,默认10
|
|
* @returns {Object} 清理后的缓存对象
|
|
*/
|
|
export function limitCacheSize(audioCache, maxSize = 10) {
|
|
const cacheKeys = Object.keys(audioCache);
|
|
if (cacheKeys.length > maxSize) {
|
|
// 删除最旧的缓存项
|
|
const keysToDelete = cacheKeys.slice(0, cacheKeys.length - maxSize);
|
|
keysToDelete.forEach(key => {
|
|
delete audioCache[key];
|
|
});
|
|
}
|
|
return audioCache;
|
|
}
|
|
|
|
/**
|
|
* 清理音频缓存
|
|
* @param {Object} audioCache - 音频缓存对象
|
|
* @returns {Object} 清空的缓存对象
|
|
*/
|
|
export function clearAudioCache(audioCache) {
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* 音频状态重置工具
|
|
* @param {Object} audioState - 音频状态对象
|
|
* @param {boolean} clearHighlight - 是否清除高亮索引,默认true
|
|
* @returns {Object} 重置后的状态对象
|
|
*/
|
|
export function resetAudioState(audioState, clearHighlight = true) {
|
|
const resetState = {
|
|
...audioState,
|
|
isPlaying: false,
|
|
currentTime: 0,
|
|
sliderValue: 0
|
|
};
|
|
|
|
if (clearHighlight) {
|
|
resetState.currentHighlightIndex = -1;
|
|
}
|
|
|
|
return resetState;
|
|
}
|
|
|
|
/**
|
|
* 检查音频数据是否属于当前页面
|
|
* @param {Object} audioData - 音频数据
|
|
* @param {string} expectedCacheKey - 期望的缓存键
|
|
* @returns {boolean} 是否属于当前页面
|
|
*/
|
|
export function isAudioDataForCurrentPage(audioData, expectedCacheKey) {
|
|
if (!audioData || !audioData.cacheKey) {
|
|
return false;
|
|
}
|
|
return audioData.cacheKey === expectedCacheKey;
|
|
}
|