feat: 优化音频播放进度更新逻辑,添加拖动滑块时的状态管理和节流处理

This commit is contained in:
alger
2025-03-31 22:57:00 +08:00
parent 230132904e
commit cfe197c805
4 changed files with 145 additions and 18 deletions

View File

@@ -60,6 +60,7 @@ const { message } = createDiscreteApi(['message']);
// 全局变量 // 全局变量
let progressAnimationInitialized = false; let progressAnimationInitialized = false;
let globalAnimationFrameId: number | null = null; let globalAnimationFrameId: number | null = null;
const lastSavedTime = ref(0);
// 全局停止函数 // 全局停止函数
const stopProgressAnimation = () => { const stopProgressAnimation = () => {
@@ -104,10 +105,21 @@ const updateProgress = () => {
let currentTime; let currentTime;
try { try {
// 获取当前播放位置
currentTime = currentSound.seek() as number; currentTime = currentSound.seek() as number;
// 减少更新频率避免频繁更新UI
const timeDiff = Math.abs(currentTime - nowTime.value);
if (timeDiff > 0.2 || Math.floor(currentTime) !== Math.floor(nowTime.value)) {
nowTime.value = currentTime;
}
// 保存当前播放进度到 localStorage (每秒保存一次,避免频繁写入) // 保存当前播放进度到 localStorage (每秒保存一次,避免频繁写入)
if (Math.floor(currentTime) % 2 === 0) { if (
Math.floor(currentTime) % 2 === 0 &&
Math.floor(currentTime) !== Math.floor(lastSavedTime.value)
) {
lastSavedTime.value = currentTime;
if (playerStore.playMusic && playerStore.playMusic.id) { if (playerStore.playMusic && playerStore.playMusic.id) {
localStorage.setItem( localStorage.setItem(
'playProgress', 'playProgress',
@@ -140,8 +152,10 @@ const updateProgress = () => {
console.error('更新进度出错:', error); console.error('更新进度出错:', error);
} }
// 继续下一帧更新 // 继续下一帧更新但降低更新频率为60帧中更新10帧
globalAnimationFrameId = requestAnimationFrame(updateProgress); globalAnimationFrameId = setTimeout(() => {
requestAnimationFrame(updateProgress);
}, 100) as unknown as number;
}; };
// 全局启动函数 // 全局启动函数
@@ -326,6 +340,37 @@ const setupAudioListeners = () => {
// 清理所有事件监听器 // 清理所有事件监听器
audioService.clearAllListeners(); audioService.clearAllListeners();
// 监听seek开始事件立即更新UI
audioService.on('seek_start', (time) => {
// 直接更新显示位置,不检查拖动状态
nowTime.value = time;
});
// 监听seek完成事件
audioService.on('seek', () => {
try {
const currentSound = sound.value;
if (currentSound) {
// 立即更新显示时间,不进行任何检查
const currentTime = currentSound.seek() as number;
if (typeof currentTime === 'number' && !Number.isNaN(currentTime)) {
nowTime.value = currentTime;
// 检查是否需要更新歌词
const newIndex = getLrcIndex(nowTime.value);
if (newIndex !== nowIndex.value) {
nowIndex.value = newIndex;
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
}
}
} catch (error) {
console.error('处理seek事件出错:', error);
}
});
// 立即更新一次时间和进度(解决初始化时进度条不显示问题) // 立即更新一次时间和进度(解决初始化时进度条不显示问题)
const updateCurrentTimeAndDuration = () => { const updateCurrentTimeAndDuration = () => {
const currentSound = audioService.getCurrentSound(); const currentSound = audioService.getCurrentSound();

View File

@@ -28,6 +28,8 @@
:show-tooltip="showSliderTooltip" :show-tooltip="showSliderTooltip"
@mouseenter="showSliderTooltip = true" @mouseenter="showSliderTooltip = true"
@mouseleave="showSliderTooltip = false" @mouseleave="showSliderTooltip = false"
@dragstart="handleSliderDragStart"
@dragend="handleSliderDragEnd"
></n-slider> ></n-slider>
</div> </div>
<div class="play-bar-img-wrapper" @click="setMusicFull"> <div class="play-bar-img-wrapper" @click="setMusicFull">
@@ -199,7 +201,6 @@ import {
nowTime, nowTime,
openLyric, openLyric,
playMusic, playMusic,
sound,
textColors textColors
} from '@/hooks/MusicHook'; } from '@/hooks/MusicHook';
import { useArtist } from '@/hooks/useArtist'; import { useArtist } from '@/hooks/useArtist';
@@ -233,17 +234,47 @@ watch(
// 节流版本的 seek 函数 // 节流版本的 seek 函数
const throttledSeek = useThrottleFn((value: number) => { const throttledSeek = useThrottleFn((value: number) => {
if (!sound.value) return; audioService.seek(value);
sound.value.seek(value);
nowTime.value = value; nowTime.value = value;
}, 50); // 50ms 的节流延迟 }, 50); // 50ms 的节流延迟
// 拖动时的临时值,避免频繁更新 nowTime 触发重渲染
const dragValue = ref(0);
// 为滑块拖动添加状态跟踪
const isDragging = ref(false);
// 修改 timeSlider 计算属性 // 修改 timeSlider 计算属性
const timeSlider = computed({ const timeSlider = computed({
get: () => nowTime.value, get: () => (isDragging.value ? dragValue.value : nowTime.value),
set: throttledSeek set: (value) => {
if (isDragging.value) {
// 拖动中只更新临时值,不触发 nowTime 更新和 seek 操作
dragValue.value = value;
return;
}
// 点击操作 (非拖动),可以直接 seek
throttledSeek(value);
}
}); });
// 添加滑块拖动开始和结束事件处理
const handleSliderDragStart = () => {
isDragging.value = true;
// 初始化拖动值为当前时间
dragValue.value = nowTime.value;
};
const handleSliderDragEnd = () => {
isDragging.value = false;
// 直接应用最终的拖动值
audioService.seek(dragValue.value);
nowTime.value = dragValue.value;
};
// 格式化提示文本,根据拖动状态显示不同的时间
const formatTooltip = (value: number) => { const formatTooltip = (value: number) => {
return `${secondToMinute(value)} / ${secondToMinute(allTime.value)}`; return `${secondToMinute(value)} / ${secondToMinute(allTime.value)}`;
}; };
@@ -266,9 +297,8 @@ const getVolumeIcon = computed(() => {
const volumeSlider = computed({ const volumeSlider = computed({
get: () => audioVolume.value * 100, get: () => audioVolume.value * 100,
set: (value) => { set: (value) => {
if (!sound.value) return;
localStorage.setItem('volume', (value / 100).toString()); localStorage.setItem('volume', (value / 100).toString());
sound.value.volume(value / 100); audioService.setVolume(value / 100);
audioVolume.value = value / 100; audioVolume.value = value / 100;
} }
}); });

View File

@@ -37,6 +37,10 @@ class AudioService {
private retryCount = 0; private retryCount = 0;
private seekLock = false;
private seekDebounceTimer: NodeJS.Timeout | null = null;
constructor() { constructor() {
if ('mediaSession' in navigator) { if ('mediaSession' in navigator) {
this.initMediaSession(); this.initMediaSession();
@@ -61,21 +65,22 @@ class AudioService {
navigator.mediaSession.setActionHandler('seekto', (event) => { navigator.mediaSession.setActionHandler('seekto', (event) => {
if (event.seekTime && this.currentSound) { if (event.seekTime && this.currentSound) {
this.currentSound.seek(event.seekTime); // this.currentSound.seek(event.seekTime);
this.seek(event.seekTime);
} }
}); });
navigator.mediaSession.setActionHandler('seekbackward', (event) => { navigator.mediaSession.setActionHandler('seekbackward', (event) => {
if (this.currentSound) { if (this.currentSound) {
const currentTime = this.currentSound.seek() as number; const currentTime = this.currentSound.seek() as number;
this.currentSound.seek(currentTime - (event.seekOffset || 10)); this.seek(currentTime - (event.seekOffset || 10));
} }
}); });
navigator.mediaSession.setActionHandler('seekforward', (event) => { navigator.mediaSession.setActionHandler('seekforward', (event) => {
if (this.currentSound) { if (this.currentSound) {
const currentTime = this.currentSound.seek() as number; const currentTime = this.currentSound.seek() as number;
this.currentSound.seek(currentTime + (event.seekOffset || 10)); this.seek(currentTime + (event.seekOffset || 10));
} }
}); });
@@ -355,6 +360,11 @@ class AudioService {
play(url?: string, track?: SongResult, isPlay: boolean = true): Promise<Howl> { play(url?: string, track?: SongResult, isPlay: boolean = true): Promise<Howl> {
// 如果没有提供新的 URL 和 track且当前有音频实例则继续播放 // 如果没有提供新的 URL 和 track且当前有音频实例则继续播放
if (this.currentSound && !url && !track) { if (this.currentSound && !url && !track) {
// 如果有进行中的seek操作等待其完成
if (this.seekLock && this.seekDebounceTimer) {
clearTimeout(this.seekDebounceTimer);
this.seekLock = false;
}
this.currentSound.play(); this.currentSound.play();
return Promise.resolve(this.currentSound); return Promise.resolve(this.currentSound);
} }
@@ -396,6 +406,11 @@ class AudioService {
// 先停止并清理现有的音频实例 // 先停止并清理现有的音频实例
if (this.currentSound) { if (this.currentSound) {
console.log('audioService: 停止并清理现有的音频实例'); console.log('audioService: 停止并清理现有的音频实例');
// 确保任何进行中的seek操作被取消
if (this.seekLock && this.seekDebounceTimer) {
clearTimeout(this.seekDebounceTimer);
this.seekLock = false;
}
this.currentSound.stop(); this.currentSound.stop();
this.currentSound.unload(); this.currentSound.unload();
this.currentSound = null; this.currentSound = null;
@@ -511,6 +526,11 @@ class AudioService {
stop() { stop() {
if (this.currentSound) { if (this.currentSound) {
try { try {
// 确保任何进行中的seek操作被取消
if (this.seekLock && this.seekDebounceTimer) {
clearTimeout(this.seekDebounceTimer);
this.seekLock = false;
}
this.currentSound.stop(); this.currentSound.stop();
this.currentSound.unload(); this.currentSound.unload();
} catch (error) { } catch (error) {
@@ -534,14 +554,26 @@ class AudioService {
seek(time: number) { seek(time: number) {
if (this.currentSound) { if (this.currentSound) {
try {
// 直接执行seek操作避免任何过滤或判断
this.currentSound.seek(time); this.currentSound.seek(time);
// 触发seek事件
this.updateMediaSessionPositionState(); this.updateMediaSessionPositionState();
this.emit('seek', time);
} catch (error) {
console.error('Seek操作失败:', error);
}
} }
} }
pause() { pause() {
if (this.currentSound) { if (this.currentSound) {
try { try {
// 如果有进行中的seek操作等待其完成
if (this.seekLock && this.seekDebounceTimer) {
clearTimeout(this.seekDebounceTimer);
this.seekLock = false;
}
this.currentSound.pause(); this.currentSound.pause();
} catch (error) { } catch (error) {
console.error('Error pausing audio:', error); console.error('Error pausing audio:', error);

View File

@@ -165,12 +165,27 @@ const getSongDetail = async (playMusic: SongResult) => {
const preloadNextSong = (nextSongUrl: string) => { const preloadNextSong = (nextSongUrl: string) => {
try { try {
if (preloadingSounds.value.length >= 2) { // 清理多余的预加载实例确保最多只有2个预加载音频
while (preloadingSounds.value.length >= 2) {
const oldestSound = preloadingSounds.value.shift(); const oldestSound = preloadingSounds.value.shift();
if (oldestSound) { if (oldestSound) {
try {
oldestSound.stop();
oldestSound.unload(); oldestSound.unload();
} catch (e) {
console.error('清理预加载音频实例失败:', e);
} }
} }
}
// 检查这个URL是否已经在预加载列表中
const existingPreload = preloadingSounds.value.find(
(sound) => (sound as any)._src === nextSongUrl
);
if (existingPreload) {
console.log('该音频已在预加载列表中,跳过:', nextSongUrl);
return existingPreload;
}
const sound = new Howl({ const sound = new Howl({
src: [nextSongUrl], src: [nextSongUrl],
@@ -187,7 +202,12 @@ const preloadNextSong = (nextSongUrl: string) => {
if (index > -1) { if (index > -1) {
preloadingSounds.value.splice(index, 1); preloadingSounds.value.splice(index, 1);
} }
try {
sound.stop();
sound.unload(); sound.unload();
} catch (e) {
console.error('卸载预加载音频失败:', e);
}
}); });
return sound; return sound;