mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-23 23:57:22 +08:00
Merge pull request #173 from algerkong/fix/overlapping-playback
🐞 fix: 修复音乐播放重复声音的问题,添加锁机制,添加防抖机制,优化音频服务和快捷键处理逻辑
This commit is contained in:
@@ -41,6 +41,9 @@ class AudioService {
|
|||||||
|
|
||||||
private seekDebounceTimer: NodeJS.Timeout | null = null;
|
private seekDebounceTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
// 添加操作锁防止并发操作
|
||||||
|
private operationLock = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
this.initMediaSession();
|
this.initMediaSession();
|
||||||
@@ -358,6 +361,14 @@ class AudioService {
|
|||||||
|
|
||||||
// 播放控制相关
|
// 播放控制相关
|
||||||
play(url?: string, track?: SongResult, isPlay: boolean = true): Promise<Howl> {
|
play(url?: string, track?: SongResult, isPlay: boolean = true): Promise<Howl> {
|
||||||
|
// 如果操作锁已激活,说明有操作正在进行中,直接返回
|
||||||
|
if (this.operationLock) {
|
||||||
|
console.log('audioService: 操作锁激活,忽略当前播放请求');
|
||||||
|
return Promise.reject(new Error('操作锁激活,请等待当前操作完成'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationLock = true;
|
||||||
|
|
||||||
// 如果没有提供新的 URL 和 track,且当前有音频实例,则继续播放
|
// 如果没有提供新的 URL 和 track,且当前有音频实例,则继续播放
|
||||||
if (this.currentSound && !url && !track) {
|
if (this.currentSound && !url && !track) {
|
||||||
// 如果有进行中的seek操作,等待其完成
|
// 如果有进行中的seek操作,等待其完成
|
||||||
@@ -366,15 +377,17 @@ class AudioService {
|
|||||||
this.seekLock = false;
|
this.seekLock = false;
|
||||||
}
|
}
|
||||||
this.currentSound.play();
|
this.currentSound.play();
|
||||||
|
this.operationLock = false;
|
||||||
return Promise.resolve(this.currentSound);
|
return Promise.resolve(this.currentSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有提供必要的参数,返回错误
|
// 如果没有提供必要的参数,返回错误
|
||||||
if (!url || !track) {
|
if (!url || !track) {
|
||||||
|
this.operationLock = false;
|
||||||
return Promise.reject(new Error('Missing required parameters: url and track'));
|
return Promise.reject(new Error('Missing required parameters: url and track'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<Howl>((resolve, reject) => {
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = 1;
|
const maxRetries = 1;
|
||||||
|
|
||||||
@@ -507,11 +520,15 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating audio instance:', error);
|
console.error('Error creating audio instance:', error);
|
||||||
|
this.operationLock = false;
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tryPlay();
|
tryPlay();
|
||||||
|
}).finally(() => {
|
||||||
|
// 无论成功或失败都解除操作锁
|
||||||
|
this.operationLock = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,6 +541,13 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
if (this.operationLock) {
|
||||||
|
console.log('audioService: 操作锁激活,忽略当前停止请求');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationLock = true;
|
||||||
|
|
||||||
if (this.currentSound) {
|
if (this.currentSound) {
|
||||||
try {
|
try {
|
||||||
// 确保任何进行中的seek操作被取消
|
// 确保任何进行中的seek操作被取消
|
||||||
@@ -538,11 +562,14 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
this.currentSound = null;
|
this.currentSound = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTrack = null;
|
this.currentTrack = null;
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
navigator.mediaSession.playbackState = 'none';
|
navigator.mediaSession.playbackState = 'none';
|
||||||
}
|
}
|
||||||
this.disposeEQ();
|
this.disposeEQ();
|
||||||
|
|
||||||
|
this.operationLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolume(volume: number) {
|
setVolume(volume: number) {
|
||||||
@@ -553,6 +580,13 @@ class AudioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seek(time: number) {
|
seek(time: number) {
|
||||||
|
if (this.operationLock) {
|
||||||
|
console.log('audioService: 操作锁激活,忽略当前seek请求');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationLock = true;
|
||||||
|
|
||||||
if (this.currentSound) {
|
if (this.currentSound) {
|
||||||
try {
|
try {
|
||||||
// 直接执行seek操作,避免任何过滤或判断
|
// 直接执行seek操作,避免任何过滤或判断
|
||||||
@@ -564,9 +598,18 @@ class AudioService {
|
|||||||
console.error('Seek操作失败:', error);
|
console.error('Seek操作失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.operationLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
|
if (this.operationLock) {
|
||||||
|
console.log('audioService: 操作锁激活,忽略当前暂停请求');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.operationLock = true;
|
||||||
|
|
||||||
if (this.currentSound) {
|
if (this.currentSound) {
|
||||||
try {
|
try {
|
||||||
// 如果有进行中的seek操作,等待其完成
|
// 如果有进行中的seek操作,等待其完成
|
||||||
@@ -579,6 +622,8 @@ class AudioService {
|
|||||||
console.error('Error pausing audio:', error);
|
console.error('Error pausing audio:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.operationLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllListeners() {
|
clearAllListeners() {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { usePlayerStore, useSettingsStore } from '@/store';
|
|||||||
import { isElectron } from '.';
|
import { isElectron } from '.';
|
||||||
import { showShortcutToast } from './shortcutToast';
|
import { showShortcutToast } from './shortcutToast';
|
||||||
|
|
||||||
|
// 添加一个简单的防抖机制
|
||||||
|
let actionTimeout: NodeJS.Timeout | null = null;
|
||||||
|
const ACTION_DELAY = 300; // 毫秒
|
||||||
|
|
||||||
interface ShortcutConfig {
|
interface ShortcutConfig {
|
||||||
key: string;
|
key: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -27,6 +31,17 @@ let appShortcuts: ShortcutsConfig = {};
|
|||||||
* @param action 快捷键动作
|
* @param action 快捷键动作
|
||||||
*/
|
*/
|
||||||
export async function handleShortcutAction(action: string) {
|
export async function handleShortcutAction(action: string) {
|
||||||
|
// 如果存在未完成的动作,则忽略当前请求
|
||||||
|
if (actionTimeout) {
|
||||||
|
console.log('忽略快速连续的动作请求:', action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置防抖锁
|
||||||
|
actionTimeout = setTimeout(() => {
|
||||||
|
actionTimeout = null;
|
||||||
|
}, ACTION_DELAY);
|
||||||
|
|
||||||
const playerStore = usePlayerStore();
|
const playerStore = usePlayerStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
@@ -38,61 +53,71 @@ export async function handleShortcutAction(action: string) {
|
|||||||
showShortcutToast(message, iconName);
|
showShortcutToast(message, iconName);
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (action) {
|
try {
|
||||||
case 'togglePlay':
|
switch (action) {
|
||||||
if (playerStore.play) {
|
case 'togglePlay':
|
||||||
await audioService.pause();
|
if (playerStore.play) {
|
||||||
showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
|
await audioService.pause();
|
||||||
} else {
|
showToast(t('player.playBar.pause'), 'ri-pause-circle-line');
|
||||||
await audioService.play();
|
} else {
|
||||||
showToast(t('player.playBar.play'), 'ri-play-circle-line');
|
await audioService.play();
|
||||||
}
|
showToast(t('player.playBar.play'), 'ri-play-circle-line');
|
||||||
break;
|
}
|
||||||
case 'prevPlay':
|
break;
|
||||||
playerStore.prevPlay();
|
case 'prevPlay':
|
||||||
showToast(t('player.playBar.prev'), 'ri-skip-back-line');
|
await playerStore.prevPlay();
|
||||||
break;
|
showToast(t('player.playBar.prev'), 'ri-skip-back-line');
|
||||||
case 'nextPlay':
|
break;
|
||||||
playerStore.nextPlay();
|
case 'nextPlay':
|
||||||
showToast(t('player.playBar.next'), 'ri-skip-forward-line');
|
await playerStore.nextPlay();
|
||||||
break;
|
showToast(t('player.playBar.next'), 'ri-skip-forward-line');
|
||||||
case 'volumeUp':
|
break;
|
||||||
if (currentSound && currentSound?.volume() < 1) {
|
case 'volumeUp':
|
||||||
currentSound?.volume((currentSound?.volume() || 0) + 0.1);
|
if (currentSound && currentSound?.volume() < 1) {
|
||||||
|
currentSound?.volume((currentSound?.volume() || 0) + 0.1);
|
||||||
|
showToast(
|
||||||
|
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
||||||
|
'ri-volume-up-line'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'volumeDown':
|
||||||
|
if (currentSound && currentSound?.volume() > 0) {
|
||||||
|
currentSound?.volume((currentSound?.volume() || 0) - 0.1);
|
||||||
|
showToast(
|
||||||
|
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
||||||
|
'ri-volume-down-line'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'toggleFavorite': {
|
||||||
|
const isFavorite = playerStore.favoriteList.includes(Number(playerStore.playMusic.id));
|
||||||
|
const numericId = Number(playerStore.playMusic.id);
|
||||||
|
if (isFavorite) {
|
||||||
|
playerStore.removeFromFavorite(numericId);
|
||||||
|
} else {
|
||||||
|
playerStore.addToFavorite(numericId);
|
||||||
|
}
|
||||||
showToast(
|
showToast(
|
||||||
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
isFavorite
|
||||||
'ri-volume-up-line'
|
? t('player.playBar.favorite', { name: playerStore.playMusic.name })
|
||||||
|
: t('player.playBar.unFavorite', { name: playerStore.playMusic.name }),
|
||||||
|
isFavorite ? 'ri-heart-fill' : 'ri-heart-line'
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
default:
|
||||||
case 'volumeDown':
|
console.log('未知的快捷键动作:', action);
|
||||||
if (currentSound && currentSound?.volume() > 0) {
|
break;
|
||||||
currentSound?.volume((currentSound?.volume() || 0) - 0.1);
|
}
|
||||||
showToast(
|
} catch (error) {
|
||||||
`${t('player.playBar.volume')}${Math.round((currentSound?.volume() || 0) * 100)}%`,
|
console.error(`执行快捷键动作 ${action} 时出错:`, error);
|
||||||
'ri-volume-down-line'
|
} finally {
|
||||||
);
|
// 确保在出错时也能清除超时
|
||||||
}
|
if (actionTimeout) {
|
||||||
break;
|
clearTimeout(actionTimeout);
|
||||||
case 'toggleFavorite': {
|
actionTimeout = null;
|
||||||
const isFavorite = playerStore.favoriteList.includes(Number(playerStore.playMusic.id));
|
|
||||||
const numericId = Number(playerStore.playMusic.id);
|
|
||||||
if (isFavorite) {
|
|
||||||
playerStore.removeFromFavorite(numericId);
|
|
||||||
} else {
|
|
||||||
playerStore.addToFavorite(numericId);
|
|
||||||
}
|
|
||||||
showToast(
|
|
||||||
isFavorite
|
|
||||||
? t('player.playBar.favorite', { name: playerStore.playMusic.name })
|
|
||||||
: t('player.playBar.unFavorite', { name: playerStore.playMusic.name }),
|
|
||||||
isFavorite ? 'ri-heart-fill' : 'ri-heart-line'
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
console.log('未知的快捷键动作:', action);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user