mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-23 23:01:21 +08:00
视频解密优化
This commit is contained in:
@@ -97,7 +97,7 @@ class VideoService {
|
||||
return realMd5
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,11 +105,22 @@ class VideoService {
|
||||
|
||||
// 方法2:使用 wcdbService.execQuery 查询加密的 hardlink.db
|
||||
if (dbPath) {
|
||||
const encryptedDbPaths = [
|
||||
join(dbPath, wxid, 'db_storage', 'hardlink', 'hardlink.db'),
|
||||
join(dbPath, cleanedWxid, 'db_storage', 'hardlink', 'hardlink.db')
|
||||
]
|
||||
|
||||
// 检查 dbPath 是否已经包含 wxid
|
||||
const dbPathLower = dbPath.toLowerCase()
|
||||
const wxidLower = wxid.toLowerCase()
|
||||
const cleanedWxidLower = cleanedWxid.toLowerCase()
|
||||
const dbPathContainsWxid = dbPathLower.includes(wxidLower) || dbPathLower.includes(cleanedWxidLower)
|
||||
|
||||
const encryptedDbPaths: string[] = []
|
||||
if (dbPathContainsWxid) {
|
||||
// dbPath 已包含 wxid,不需要再拼接
|
||||
encryptedDbPaths.push(join(dbPath, 'db_storage', 'hardlink', 'hardlink.db'))
|
||||
} else {
|
||||
// dbPath 不包含 wxid,需要拼接
|
||||
encryptedDbPaths.push(join(dbPath, wxid, 'db_storage', 'hardlink', 'hardlink.db'))
|
||||
encryptedDbPaths.push(join(dbPath, cleanedWxid, 'db_storage', 'hardlink', 'hardlink.db'))
|
||||
}
|
||||
|
||||
for (const p of encryptedDbPaths) {
|
||||
if (existsSync(p)) {
|
||||
try {
|
||||
@@ -129,6 +140,7 @@ class VideoService {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +167,6 @@ class VideoService {
|
||||
* 文件命名: {md5}.mp4, {md5}.jpg, {md5}_thumb.jpg
|
||||
*/
|
||||
async getVideoInfo(videoMd5: string): Promise<VideoInfo> {
|
||||
|
||||
const dbPath = this.getDbPath()
|
||||
const wxid = this.getMyWxid()
|
||||
|
||||
@@ -166,7 +177,19 @@ class VideoService {
|
||||
// 先尝试从数据库查询真正的视频文件名
|
||||
const realVideoMd5 = await this.queryVideoFileName(videoMd5) || videoMd5
|
||||
|
||||
const videoBaseDir = join(dbPath, wxid, 'msg', 'video')
|
||||
// 检查 dbPath 是否已经包含 wxid,避免重复拼接
|
||||
const dbPathLower = dbPath.toLowerCase()
|
||||
const wxidLower = wxid.toLowerCase()
|
||||
const cleanedWxid = this.cleanWxid(wxid)
|
||||
|
||||
let videoBaseDir: string
|
||||
if (dbPathLower.includes(wxidLower) || dbPathLower.includes(cleanedWxid.toLowerCase())) {
|
||||
// dbPath 已经包含 wxid,直接使用
|
||||
videoBaseDir = join(dbPath, 'msg', 'video')
|
||||
} else {
|
||||
// dbPath 不包含 wxid,需要拼接
|
||||
videoBaseDir = join(dbPath, wxid, 'msg', 'video')
|
||||
}
|
||||
|
||||
if (!existsSync(videoBaseDir)) {
|
||||
return { exists: false }
|
||||
@@ -202,7 +225,7 @@ class VideoService {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[VideoService] Error searching for video:', e)
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
return { exists: false }
|
||||
|
||||
@@ -191,7 +191,7 @@ export class WcdbCore {
|
||||
}
|
||||
|
||||
private isLogEnabled(): boolean {
|
||||
if (process.env.WEFLOW_WORKER === '1') return false
|
||||
// 移除 Worker 线程的日志禁用逻辑,允许在 Worker 中记录日志
|
||||
if (process.env.WCDB_LOG_ENABLED === '1') return true
|
||||
return this.logEnabled
|
||||
}
|
||||
|
||||
@@ -2146,8 +2146,7 @@
|
||||
}
|
||||
|
||||
.video-placeholder,
|
||||
.video-loading,
|
||||
.video-unavailable {
|
||||
.video-loading {
|
||||
min-width: 120px;
|
||||
min-height: 80px;
|
||||
display: flex;
|
||||
@@ -2167,6 +2166,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
.video-unavailable {
|
||||
min-width: 160px;
|
||||
min-height: 120px;
|
||||
border-radius: 12px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
-webkit-app-region: no-drag;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.clicked {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.video-action {
|
||||
font-size: 11px;
|
||||
color: var(--text-quaternary);
|
||||
}
|
||||
|
||||
.video-loading {
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
|
||||
@@ -2155,6 +2155,9 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
}, [isVoice, message.localId, requestVoiceTranscript])
|
||||
|
||||
// 视频懒加载
|
||||
const videoAutoLoadTriggered = useRef(false)
|
||||
const [videoClicked, setVideoClicked] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVideo || !videoContainerRef.current) return
|
||||
|
||||
@@ -2178,19 +2181,18 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
return () => observer.disconnect()
|
||||
}, [isVideo])
|
||||
|
||||
// 加载视频信息
|
||||
useEffect(() => {
|
||||
if (!isVideo || !isVideoVisible || videoInfo || videoLoading) return
|
||||
if (!videoMd5) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 视频加载中状态引用,避免依赖问题
|
||||
const videoLoadingRef = useRef(false)
|
||||
|
||||
// 加载视频信息(添加重试机制)
|
||||
const requestVideoInfo = useCallback(async () => {
|
||||
if (!videoMd5 || videoLoadingRef.current) return
|
||||
|
||||
videoLoadingRef.current = true
|
||||
setVideoLoading(true)
|
||||
window.electronAPI.video.getVideoInfo(videoMd5).then((result: { success: boolean; exists: boolean; videoUrl?: string; coverUrl?: string; thumbUrl?: string; error?: string }) => {
|
||||
|
||||
if (result && result.success) {
|
||||
try {
|
||||
const result = await window.electronAPI.video.getVideoInfo(videoMd5)
|
||||
if (result && result.success && result.exists) {
|
||||
setVideoInfo({
|
||||
exists: result.exists,
|
||||
videoUrl: result.videoUrl,
|
||||
@@ -2198,16 +2200,25 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
thumbUrl: result.thumbUrl
|
||||
})
|
||||
} else {
|
||||
console.error('[Video Debug] Video info failed:', result)
|
||||
setVideoInfo({ exists: false })
|
||||
}
|
||||
}).catch((err: unknown) => {
|
||||
console.error('[Video Debug] getVideoInfo error:', err)
|
||||
} catch (err) {
|
||||
setVideoInfo({ exists: false })
|
||||
}).finally(() => {
|
||||
} finally {
|
||||
videoLoadingRef.current = false
|
||||
setVideoLoading(false)
|
||||
})
|
||||
}, [isVideo, isVideoVisible, videoInfo, videoLoading, videoMd5])
|
||||
}
|
||||
}, [videoMd5])
|
||||
|
||||
// 视频进入视野时自动加载
|
||||
useEffect(() => {
|
||||
if (!isVideo || !isVideoVisible) return
|
||||
if (videoInfo?.exists) return // 已成功加载,不需要重试
|
||||
if (videoAutoLoadTriggered.current) return
|
||||
|
||||
videoAutoLoadTriggered.current = true
|
||||
void requestVideoInfo()
|
||||
}, [isVideo, isVideoVisible, videoInfo, requestVideoInfo])
|
||||
|
||||
|
||||
// 根据设置决定是否自动转写
|
||||
@@ -2366,16 +2377,27 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
)
|
||||
}
|
||||
|
||||
// 视频不存在
|
||||
// 视频不存在 - 添加点击重试功能
|
||||
if (!videoInfo?.exists || !videoInfo.videoUrl) {
|
||||
return (
|
||||
<div className="video-unavailable" ref={videoContainerRef}>
|
||||
<button
|
||||
className={`video-unavailable ${videoClicked ? 'clicked' : ''}`}
|
||||
ref={videoContainerRef}
|
||||
onClick={() => {
|
||||
setVideoClicked(true)
|
||||
setTimeout(() => setVideoClicked(false), 800)
|
||||
videoAutoLoadTriggered.current = false
|
||||
void requestVideoInfo()
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
||||
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
<span>视频不可用</span>
|
||||
</div>
|
||||
<span>视频未找到</span>
|
||||
<span className="video-action">{videoClicked ? '已点击…' : '点击重试'}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user