[LayaAirIDE3]xcode切入切出声音不播放

官方你好
        我用的是LayaAirIDE,layaair3  version:3.3.2版本。项目是网页游戏,用WKWebView打包网页游戏。
声音在xcode里面切入切出,导致背景声音不播放问题。
 Laya.stage.on(Laya.Event.BLUR, this, this.onBlur); Laya.stage.on(Laya.Event.FOCUS, this, this.onFocus);
 private onBlur() {
        // 暂停所有需要停止的逻辑
        console.log("onBlur");
        SoundManager.Instance.pauseBGM();
        // // 部分机型包内不生效
        SoundManager.Instance.pauseAllEffects();
    }
 
 private onFocus() {
        if (!SoundManager.isPlay) {
            SoundManager.isPlay = true; 
            console.log("111111  onFocus");
            Laya.timer.once(2000, this, () => {
                console.log("222222 onFocus");
                SoundManager.isPlay = false;
                 SoundManager.Instance.playMusic111("resources/music/test/FY-0079.wav");
            });
        }
    }
 
import { JsonTool } from "src/tools/JsonTool";
import { ResourceManager } from "./ResourceManager";
declare function testmusic_cn_ios(accountId: string): void;
enum SoundType {
    BGM,
    Effect,
    Dialogue,
}
export class SoundManager {
    private static instance: SoundManager;
    private curBGMUrl: string;
    private bgmChannel: Laya.SoundChannel | null;
    private effectChannels: Map<string, Laya.SoundChannel>;
    private dialogueChannels: Map<string, Laya.SoundChannel>;
    private fadeIntervals: Map<Laya.SoundChannel, any>;
    private static readonly STORAGE_KEY = "audio_settings"; 
    public static isPlay: boolean; 
    private pausedEffects: Map<string, {
        position: number;
        completeCallback?: () => void
    }> = new Map();
    // 回调获取方法(需在setupChannelHandlers存储回调)
    private callbackMap: Map<string, () => void> = new Map();
    private lastKnownPositions: Map<string, number> = new Map(); 
    private originalBGMVolume: number = 1;
    private isBGMAttenuated: boolean = false; 
    private constructor() {
        /* 在ios中
            使用Audio标签播放(默认)无法调整音量
            使用WebAudio播放时可调整音量但会出现部分机型切后台失去焦点后无法播放所有音乐的问题
        */
        Laya.SoundManager.useAudioMusic = false;
        this.bgmChannel = null;
        this.effectChannels = new Map();
        this.dialogueChannels = new Map();
        this.fadeIntervals = new Map();
        setTimeout(() => {
            //直接调用失败,原因未知,延迟1000ms后再调用
            this.initFromStorage();
        }, 1000);
        //this.setupPositionTracker();//暂不启用
    } 
    // 初始化时读取缓存
    public initFromStorage(): void {
        const data = Laya.LocalStorage.getJSON(SoundManager.STORAGE_KEY);
        if (data) {
            //Laya.SoundManager.musicVolume = data.bgmVolume / 100;
            Laya.SoundManager.setMusicVolume(data.bgmVolume / 100);
            Laya.SoundManager.musicMuted = data.bgmMuted;
            //Laya.SoundManager.soundVolume = data.effectVolume / 100;
            Laya.SoundManager.setSoundVolume(data.effectVolume / 100);
            Laya.SoundManager.soundMuted = data.effectMuted;
        } else {
            this.setBGMVolume(30);
            this.setEffectVolume(35);
        }
    } 
    public static get Instance(): SoundManager {
        if (!SoundManager.instance) {
            SoundManager.instance = new SoundManager();
        }
        return SoundManager.instance;
    } 
    /**
     * 设置BGM音量
     * @param value 0-100
     */
    public setBGMVolume(value: number): void {
        const bgmVolume = Math.min(100, Math.max(0, value)) / 100;
        Laya.SoundManager.setMusicVolume(bgmVolume);
        /* if (this.bgmChannel) {
            this.bgmChannel.volume = Laya.SoundManager.musicVolume;
        } */
        this.saveToStorage();
    } 
    /**
     * 设置音效音量
     * @param value 0-100
     */
    public setEffectVolume(value: number): void {
        const effectVolume = Math.min(100, Math.max(0, value)) / 100;
        Laya.SoundManager.setSoundVolume(effectVolume);
        this.saveToStorage();
    } 
    // 设置音效静音
    public setEffectMuted(muted: boolean): void {
        Laya.SoundManager.soundMuted = muted;
        this.saveToStorage();
    } 
    // 设置BGM静音
    public setBGMMuted(muted: boolean): void {
        Laya.SoundManager.musicMuted = muted;
        this.saveToStorage();
    } 
    // 获取音效静音状态
    public getEffectMuted(): boolean {
        return Laya.SoundManager.soundMuted;
    } 
    // 获取BGM静音状态
    public getBGMMuted(): boolean {
        return Laya.SoundManager.musicMuted;
    } 
    // 获取BGM音量
    public getBGMVolume(): number {
        return Laya.SoundManager.musicVolume * 100;
    } 
    // 获取音效音量
    public getEffectVolume(): number {
        return Laya.SoundManager.soundVolume * 100;
    } 
    // 统一保存到本地存储
    private saveToStorage(): void {
        Laya.LocalStorage.setJSON(SoundManager.STORAGE_KEY, {
            bgmVolume: Laya.SoundManager.musicVolume * 100,
            bgmMuted: Laya.SoundManager.musicMuted,
            effectVolume: Laya.SoundManager.soundVolume * 100,
            effectMuted: Laya.SoundManager.soundMuted
        });
    } 
    /**
     * 临时衰减BGM音量
     * @param ratio 衰减比例 (0-1),例如0.5表示降低到50%
     * @param fadeDuration 渐变过渡时间(毫秒),默认立即生效
     */
    public attenuateBGM(ratio: number, fadeDuration: number = 0): void {
        if (!this.bgmChannel || ratio < 0 || ratio > 1) return; 
        const targetVolume = Laya.SoundManager.musicVolume * ratio;
        this.originalBGMVolume = this.bgmChannel.volume; 
        if (fadeDuration > 0) {
            this.fadeToVolume(this.bgmChannel, targetVolume, fadeDuration);
        } else {
            this.bgmChannel.volume = targetVolume;
        }
        this.isBGMAttenuated = true;
    } 
    /**
     * 恢复原始BGM音量
     * @param fadeDuration 渐变过渡时间(毫秒),默认立即生效
     */
    public restoreBGM(fadeDuration: number = 0): void {
        if (!this.bgmChannel || !this.isBGMAttenuated) return; 
        const targetVolume = Laya.SoundManager.musicVolume; 
        if (fadeDuration > 0) {
            this.fadeToVolume(this.bgmChannel, targetVolume, fadeDuration);
        } else {
            this.bgmChannel.volume = targetVolume;
        }
        this.isBGMAttenuated = false;
    } 
    /**
     * 播放BGM
     * @param url BGM资源路径或ID
     * @param isBreak 若地址相同时是否打断当前bgm播放
     * @returns
     */
    public playBGM(url: string | number, isBreak: boolean = true): void {
        if (!url) {
            console.warn("SoundManager: BGM URL不能为空");
            return;
        } 
        let urlStr: string;
        if (typeof url === "number") {
            const config = JsonTool.getExcelData("ResourceMusic", url);
            if (!config?.url) {
                console.error(`SoundManager: 无效的BGM ID [${url}]`);
                return;
            }
            urlStr = config.url;
        } else
            urlStr = url; 
        if (isBreak && this.curBGMUrl === urlStr) return;
        this.curBGMUrl = urlStr;
        this.stopBGM();
        try {
            ResourceManager.Instance.load(urlStr).then(() => {
                this.bgmChannel = Laya.SoundManager.playMusic(urlStr, 0);
            });
        } catch (e) {
            console.error(`SoundManager: BGM播放失败 [${url}]`, e);
        }
    } 
    public stopBGM(): void {
        if (this.bgmChannel) {
            Laya.SoundManager.stopMusic();
            this.bgmChannel = null;
        }
    } 
    public pauseBGM(): void {
        if (this.bgmChannel) {
            this.bgmChannel.pause();
        }
    } 
    public resumeBGM(): void {
        if (this.bgmChannel) {
            this.bgmChannel.resume();
        } else {
            this.playBGM(this.curBGMUrl);
        }
    } 
    public playEffect(
        url: string,
        completeCallback?: () => void,
        loop: boolean = false,
        startTime: number = 0
    ): Laya.SoundChannel | null {
        if (!url) {
            console.warn("SoundManager: 音效URL不能为空");
            return null;
        } 
        if (Laya.SoundManager.soundMuted) return; 
        this.cleanupEffectChannel(SoundType.Effect, url); 
        try {
            const func = () => {
                const channel = Laya.SoundManager.playSound(url, loop ? 0 : 1, null, null, startTime);
                if (!channel || channel.isStopped) {
                    throw new Error("音效通道创建失败");
                } 
                this.setupChannelHandlers(SoundType.Effect, url, channel, completeCallback, loop);
                return channel;
            }
            const music = ResourceManager.Instance.getRes(url);
            if (music) {
                func();
            } else {
                ResourceManager.Instance.load(url).then(func);
            }
        } catch (e) {
            console.error(`SoundManager: 音效播放失败 [${url}]`, e);
            return null;
        }
    } 
    // 获取当前播放音效的排序
    private getActiveEffectOrder(): number {
        const activeEffect = Array.from(this.effectChannels.values())
            .find(channel =>
                channel && !channel.isStopped
            );
        if (activeEffect) {
            const active = JsonTool.getExcelData("ResourceMusic").find((data: any) => {
                return data.url === activeEffect.url
            });
            return active?.order || -1;
        }
        return -1;
    } 
    public playEffectById(
        id: number,
        completeCallback?: () => void,
        loop: boolean = false
    ): Laya.SoundChannel | null {
        const config = JsonTool.getExcelData("ResourceMusic", id);
        if (!config?.url) {
            console.error(`SoundManager: 无效的音效ID [${id}]`);
            return null;
        } 
        //相同id的音效,不播
        const activeEffect = Array.from(this.effectChannels.values())
            .find(channel =>
                channel && !channel.isStopped
            );
        if (activeEffect && activeEffect.url === config.url && config.mutualExclusion)
            return null; 
        // 当前有正在播放的音效,决定是否根据order排序播放
        const activeEffectOrder = this.getActiveEffectOrder();
        const configOrder = config.order || 0;
        if (activeEffectOrder >= 0) {
            if (configOrder > activeEffectOrder) {
                //暂停当前音效
                if (activeEffect) {
                    this.stopEffect(activeEffect.url);
                }
                //播放新音效
                console.log(`SoundManager: 更高级音效播放ID: [${id}]`);
                return this.playEffect(config.url, completeCallback, loop);
            } else if (configOrder == activeEffectOrder) {
                console.log(`SoundManager: 同级音效播放ID: [${id}]`);
                return this.playEffect(config.url, completeCallback, loop);
            }
        } else {
            console.log(`SoundManager: 音效播放ID: [${id}]`);
            return this.playEffect(config.url, completeCallback, loop);
        }
    } 
    public playDialogueById(
        id: number,
        completeCallback?: () => void,
        loop: boolean = false
    ): Laya.SoundChannel | null {
        const config = JsonTool.getExcelData("ResourceDialogue", id);
        if (!config?.url) {
            console.error(`SoundManager: 无效的音效ID [${id}]`);
            return null;
        } 
        return this.playDialogue(config.url, completeCallback, loop);
    } 
    private playDialogue(
        url: string,
        completeCallback?: () => void,
        loop: boolean = false,
        startTime: number = 0
    ): Laya.SoundChannel | null {
        if (!url) {
            console.warn("SoundManager: 音效URL不能为空");
            return null;
        } 
        if (Laya.SoundManager.soundMuted) return; 
        this.cleanupEffectChannel(SoundType.Dialogue, url); 
        try {
            const func = () => {
                //停止其他对话
                this.dialogueChannels.forEach(channel => {
                    if (channel && !channel.isStopped) {
                        this.stopEffect(channel.url, SoundType.Dialogue);
                    }
                }); 
                const channel = Laya.SoundManager.playSound(url, loop ? 0 : 1, null, null, startTime);
                if (!channel || channel.isStopped) {
                    throw new Error("音效通道创建失败");
                } 
                this.setupChannelHandlers(SoundType.Dialogue, url, channel, completeCallback, loop);
                return channel;
            }
            const music = ResourceManager.Instance.getRes(url);
            if (music) {
                func();
            } else {
                ResourceManager.Instance.load(url).then(func);
            }
        } catch (e) {
            console.error(`SoundManager: 音效播放失败 [${url}]`, e);
            return null;
        }
    } 
    public stopDialogueById(id: number): void {
        const config = JsonTool.getExcelData("ResourceDialogue", id);
        if (!config?.url) {
            console.error(`SoundManager: 无效的音效ID [${id}]`);
            return;
        }
        this.stopEffect(config.url, SoundType.Dialogue);
    } 
    public stopEffect(url: string | number, type: SoundType = SoundType.Effect): void {
        if (!url) {
            console.warn("SoundManager: 音效URL不能为空");
            return;
        } 
        let urlStr: string;
        if (typeof url === "number") {
            const config = JsonTool.getExcelData("ResourceMusic", url);
            if (!config?.url) {
                console.error(`SoundManager: 无效的音效ID [${url}]`);
                return;
            }
            urlStr = config.url;
        } else
            urlStr = url; 
        this.cleanupEffectChannel(type, urlStr);
    } 
    public getEffect(url: string): Laya.SoundChannel | undefined {
        return this.effectChannels.get(url);
    } 
    public fadeToVolume(
        channel: Laya.SoundChannel,
        targetVolume: number,
        duration: number = 1000
    ): void {
        const initialVolume = channel.volume;
        targetVolume = Math.min(1, Math.max(0, targetVolume)); 
        // 清除已有渐变
        const existingInterval = this.fadeIntervals.get(channel);
        if (existingInterval) {
            clearInterval(existingInterval);
        } 
        const startTime = Date.now();
        const updateInterval = 50; 
        const interval = setInterval(() => {
            const elapsed = Date.now() - startTime;
            const progress = Math.min(elapsed / duration, 1); 
            channel.volume = initialVolume +
                (targetVolume - initialVolume) * progress; 
            if (progress === 1) {
                clearInterval(interval);
                this.fadeIntervals.delete(channel);
            }
        }, updateInterval); 
        this.fadeIntervals.set(channel, interval);
    } 
    public destroy(): void {
        this.stopBGM();
        this.stopAllEffects();
        this.clearAllFades();
    } 
    private cleanupEffectChannel(type: SoundType, url: string): void {
        let oldChannel: Laya.SoundChannel | undefined;
        switch (type) {
            case SoundType.Effect:
                oldChannel = this.effectChannels.get(url);
                break;
            case SoundType.Dialogue:
                oldChannel = this.dialogueChannels.get(url);
                break;
        }
        if (oldChannel) {
            oldChannel.stop();
            oldChannel.offAll();
            switch (type) {
                case SoundType.Effect:
                    this.effectChannels.delete(url);
                    break;
                case SoundType.Dialogue:
                    this.dialogueChannels.delete(url);
                    break;
            }
            this.clearFade(oldChannel);
            this.lastKnownPositions.delete(url);
            this.callbackMap.delete(url);
        }
    } 
    private setupChannelHandlers(
        type: SoundType,
        url: string,
        channel: Laya.SoundChannel,
        completeCallback?: () => void,
        loop?: boolean
    ): void {
        this.callbackMap.set(url, completeCallback); // 存储回调
        const cleanUp = () => {
            channel.offAll();
            switch (type) {
                case SoundType.Effect:
                    this.effectChannels.delete(url);
                    break;
                case SoundType.Dialogue:
                    this.dialogueChannels.delete(url);
                    break;
            }
            this.clearFade(channel);
            this.lastKnownPositions.delete(url);
        }; 
        if (!loop) {
            channel.once(Laya.Event.COMPLETE, this, () => {
                completeCallback?.();
                cleanUp();
            });
        } 
        channel.once(Laya.Event.ERROR, this, (e: Error) => {
            console.error(`SoundManager: 音效播放错误 [${url}]`, e);
            cleanUp();
        }); 
        switch (type) {
            case SoundType.Effect:
                this.effectChannels.set(url, channel);
                break;
            case SoundType.Dialogue:
                this.dialogueChannels.set(url, channel);
                break;
        }
    } 
    public stopAllEffects(): void {
        this.effectChannels.forEach(channel => {
            channel.stop();
            channel.offAll();
            this.clearFade(channel);
        });
        this.effectChannels.clear();
        this.lastKnownPositions.clear();
        this.callbackMap.clear();
        this.pausedEffects.clear();
        this.dialogueChannels.forEach(channel => {
            channel.stop();
            channel.offAll();
            this.clearFade(channel);
        });
        this.dialogueChannels.clear();
    } 
    private clearAllFades(): void {
        this.fadeIntervals.forEach(interval => clearInterval(interval));
        this.fadeIntervals.clear();
    } 
    private clearFade(channel: Laya.SoundChannel): void {
        const interval = this.fadeIntervals.get(channel);
        if (interval) {
            clearInterval(interval);
            this.fadeIntervals.delete(channel);
        }
    } 
    //#region 暂停与恢复
    // 暂停方法
    public pauseAllEffects(): void {
        this.effectChannels.forEach((channel, url) => {
            if (channel) {
                // 使用双保险策略保存播放进度
                const position = this.lastKnownPositions.get(url) || channel.position || 0; 
                this.pausedEffects.set(url, {
                    position: position,
                    completeCallback: this.getCallbackForUrl(url)
                }); 
                // 立即停止
                channel.stop();
                this.lastKnownPositions.delete(url);
            }
        }); 
        // 添加延迟补偿(解决iOS后台冻结问题)
        if (Laya.Browser.onIOS) {
            Laya.timer.once(100, this, () => {
                this.lastKnownPositions.clear();
                this.effectChannels.clear();
            });
        } else {
            this.lastKnownPositions.clear();
            this.effectChannels.clear();
        }
    } 
    public playMusic111(currentBgmUrl: string): void {
        Laya.SoundManager.stopAll();
        Laya.loader.clearRes(currentBgmUrl); 
        Laya.loader.load([{ url: currentBgmUrl, type: Laya.Loader.SOUND }],
            Laya.Handler.create(this, () => {
                console.log("----playMusic111 加载完成------"); 
                // 关键:添加延迟确保音频会话就绪
                Laya.timer.once(300, this, () => {
                    const channel = Laya.SoundManager.playMusic(currentBgmUrl, 2);
                    console.log("----playMusic111 加载完成-channelchannelchannelchannel-----"); 
                    if (!channel) {
                        // 如果第一次失败,延迟重试
                        Laya.timer.once(500, this, () => {
                            Laya.SoundManager.playMusic(currentBgmUrl, 2);
                        });
                    }
                });
            })
        );
    }
    private delayedPlayWithRetry(url: string, attempt: number): void {
        const delays = [300, 600, 1000]; // 多重延迟尝试
        const maxAttempts = delays.length; 
        if (attempt >= maxAttempts) {
            console.error("达到最大重试次数,播放失败");
            return;
        } 
        const delay = delays[attempt];
        console.log(`第 ${attempt + 1} 次播放尝试,延迟 ${delay}ms`); 
        Laya.timer.once(delay, this, () => {
            const channel = Laya.SoundManager.playMusic(url, 2); 
            if (channel) {
                console.log("✅ 音乐播放成功"); 
                // 添加错误监听,在播放过程中出错时重试
                channel.on(Laya.Event.ERROR, this, (err: any) => {
                    console.error("播放过程中出错:", err);
                    this.delayedPlayWithRetry(url, 0);
                });
            } else {
                console.warn(`❌ 第 ${attempt + 1} 次播放失败`);
                // 播放失败,进行下一次重试
                this.delayedPlayWithRetry(url, attempt + 1);
            }
        });
    } 
    // 强制播放并处理状态异常
    private forcePlay(url: string) {
        // 先尝试通过 Laya 引擎播放
        const channel = Laya.SoundManager.playMusic(url, 1); 
        if (channel) {
            console.log("Laya 播放成功");
            // 监听播放过程中的异常
            channel.on(Laya.Event.ERROR, this, () => {
                console.log("播放中异常,切换到备用方案"); 
            });
        } else {
            console.log("Laya 播放失败,切换到备用方案"); 
        }
    }


    // 恢复方法
    public resumeAllEffects(): void {
        this.pausedEffects.forEach((info, url) => {
            this.playEffect(url, info.completeCallback, false, info.position);
        });
        this.pausedEffects.clear();
    } 
    private getCallbackForUrl(url: string): () => void | undefined {
        return this.callbackMap.get(url); // 获取存储的回调
    } 
    // 在每次帧更新时记录位置
    private setupPositionTracker() {
        Laya.timer.frameLoop(1, this, () => {
            this.effectChannels.forEach((channel, url) => {
                if (channel && !channel.isStopped) {
                    this.lastKnownPositions.set(url, channel.position);
                }
            });
        });
    }
    //#endregion
}
 
 
已邀请:

要回复问题请先

商务合作
商务合作