guoyu/fronted_uniapp/utils/modelDownloader.js

207 lines
4.8 KiB
JavaScript
Raw Normal View History

/**
* 语音模型下载管理器
* 用于在APP运行时下载和管理语音识别模型
*/
class ModelDownloader {
constructor() {
this.modelUrl = 'https://app.liuyingyong.cn/static/vosk-model-small-cn-0.22.zip';
this.modelFileName = 'vosk-model-small-cn-0.22.zip';
this.modelSize = 41.87 * 1024 * 1024; // 41.87 MB
}
/**
* 获取模型本地路径
*/
getModelPath() {
// #ifdef APP-PLUS
return plus.io.convertLocalFileSystemURL('_doc/' + this.modelFileName);
// #endif
// #ifndef APP-PLUS
return '';
// #endif
}
/**
* 检查模型是否已下载
*/
checkModelExists() {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
const localPath = this.getModelPath();
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
// 文件存在,检查大小
entry.file((file) => {
if (file.size > 0) {
console.log('模型文件已存在,大小:', file.size);
resolve({
exists: true,
path: localPath,
size: file.size
});
} else {
console.log('模型文件存在但大小为0需要重新下载');
resolve({
exists: false
});
}
}, () => {
resolve({
exists: false
});
});
}, () => {
// 文件不存在
console.log('模型文件不存在');
resolve({
exists: false
});
});
// #endif
// #ifndef APP-PLUS
resolve({
exists: false
});
// #endif
});
}
/**
* 下载模型
* @param {Function} onProgress 进度回调 (progress, speed)
* @param {Function} onSuccess 成功回调 (filePath)
* @param {Function} onError 失败回调 (error)
*/
downloadModel(onProgress, onSuccess, onError) {
console.log('开始下载模型:', this.modelUrl);
const localPath = this.getModelPath();
let startTime = Date.now();
let lastLoaded = 0;
const downloadTask = uni.downloadFile({
url: this.modelUrl,
filePath: localPath,
timeout: 300000, // 5分钟超时
success: (res) => {
if (res.statusCode === 200) {
console.log('模型下载成功:', res.tempFilePath);
// 验证文件大小
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.file((file) => {
if (file.size > 10 * 1024 * 1024) { // 至少10MB
console.log('模型文件验证通过,大小:', file.size);
onSuccess && onSuccess(localPath);
} else {
console.error('模型文件大小异常:', file.size);
onError && onError(new Error('文件大小异常'));
}
}, (err) => {
onError && onError(err);
});
}, (err) => {
onError && onError(err);
});
// #endif
// #ifndef APP-PLUS
onSuccess && onSuccess(localPath);
// #endif
} else {
console.error('下载失败,状态码:', res.statusCode);
onError && onError(new Error('下载失败:' + res.statusCode));
}
},
fail: (err) => {
console.error('下载失败:', err);
onError && onError(err);
}
});
// 监听下载进度
downloadTask.onProgressUpdate((res) => {
const progress = res.progress;
const totalBytesWritten = res.totalBytesWritten;
const totalBytesExpectedToWrite = res.totalBytesExpectedToWrite;
// 计算下载速度
const now = Date.now();
const elapsed = (now - startTime) / 1000; // 秒
const downloaded = totalBytesWritten - lastLoaded;
const speed = downloaded / elapsed; // 字节/秒
lastLoaded = totalBytesWritten;
startTime = now;
console.log('下载进度:', progress + '%', '速度:', (speed / 1024).toFixed(2) + ' KB/s');
onProgress && onProgress({
progress: progress,
loaded: totalBytesWritten,
total: totalBytesExpectedToWrite,
speed: speed
});
});
return downloadTask;
}
/**
* 删除模型文件
*/
deleteModel() {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
const localPath = this.getModelPath();
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
entry.remove(() => {
console.log('模型文件已删除');
resolve();
}, (err) => {
console.error('删除失败:', err);
reject(err);
});
}, () => {
// 文件不存在
resolve();
});
// #endif
// #ifndef APP-PLUS
resolve();
// #endif
});
}
/**
* 格式化文件大小
*/
formatSize(bytes) {
if (bytes < 1024) {
return bytes + ' B';
} else if (bytes < 1024 * 1024) {
return (bytes / 1024).toFixed(2) + ' KB';
} else if (bytes < 1024 * 1024 * 1024) {
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
} else {
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
}
/**
* 格式化下载速度
*/
formatSpeed(bytesPerSecond) {
return this.formatSize(bytesPerSecond) + '/s';
}
}
// 导出单例
export default new ModelDownloader();