MatchMaster/assets/Script/module/Music/AudioManager.ts
2026-07-31 17:59:07 +08:00

949 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { ccclass, property } = cc._decorator;
@ccclass
export default class AudioManager extends cc.Component {
static _instance: any;
// 远程音频 CDN 基础地址
// public CDN_AUDIO_BASE: string = "https://pay.nika4games.com/remote/music/";
public CDN_AUDIO_BASE: string = "https://cdn.pay.nika4games.com/remote/music/";
// 本地缓存的 AudioClip按需懒加载
private audioCache: { [key: string]: cc.AudioClip } = {};
// 合并同一音频的并发加载请求,避免预加载和场景播放重复下载。
private audioLoadingCallbacks: {
[key: string]: Array<(clip: cc.AudioClip) => void>
} = {};
// 远程 URL 映射表
private audioUrlMap: { [key: string]: string } = {};
// 本地持久化缓存目录(微信小游戏)
private localCacheDir: string = '';
/**
* 获取本地缓存目录
* 微信小游戏使用 USER_DATA_PATH 下的 audio_cache 子目录
*/
getLocalCacheDir(): string {
if (this.localCacheDir) return this.localCacheDir;
// @ts-ignore
if (typeof wx !== 'undefined' && wx.env && wx.env.USER_DATA_PATH) {
// @ts-ignore
this.localCacheDir = wx.env.USER_DATA_PATH + '/audio_cache/';
}
return this.localCacheDir;
}
/**
* 获取本地缓存文件路径
*/
getLocalCachePath(name: string): string {
return this.getLocalCacheDir() + name + '.mp3';
}
// 背景音乐
@property(cc.AudioClip)
main_bgm: cc.AudioClip = null;
@property(cc.AudioClip)
xiaochu: cc.AudioClip = null;
@property(cc.AudioClip)
hit: cc.AudioClip = null;
@property(cc.AudioClip)
down: cc.AudioClip = null;
@property(cc.AudioClip)
win: cc.AudioClip = null;
@property(cc.AudioClip)
anniu_Big: cc.AudioClip = null;
@property(cc.AudioClip)
anniu_little: cc.AudioClip = null;
@property(cc.AudioClip)
tanchuang: cc.AudioClip = null;
@property(cc.AudioClip)
zhuan1: cc.AudioClip = null;
@property(cc.AudioClip)
zhuan2: cc.AudioClip = null;
@property(cc.AudioClip)
adhesive: cc.AudioClip = null;
@property(cc.AudioClip)
freezeBlock: cc.AudioClip = null;
@property(cc.AudioClip)
freezeDoor: cc.AudioClip = null;
@property(cc.AudioClip)
hammer: cc.AudioClip = null;
@property(cc.AudioClip)
lockBlock1: cc.AudioClip = null;
@property(cc.AudioClip)
lockBlock2: cc.AudioClip = null;
@property(cc.AudioClip)
lockDoor: cc.AudioClip = null;
@property(cc.AudioClip)
magic1: cc.AudioClip = null;
@property(cc.AudioClip)
magic2: cc.AudioClip = null;
@property(cc.AudioClip)
simpleColor: cc.AudioClip = null;
@property(cc.AudioClip)
stacking: cc.AudioClip = null;
@property(cc.AudioClip)
starBlock: cc.AudioClip = null;
@property(cc.AudioClip)
timePause1: cc.AudioClip = null;
@property(cc.AudioClip)
timePause2: cc.AudioClip = null;
@property(cc.AudioClip)
hit1: cc.AudioClip = null;
@property(cc.AudioClip)
hit2: cc.AudioClip = null;
@property(cc.AudioClip)
hit3: cc.AudioClip = null;
@property(cc.AudioClip)
hit4: cc.AudioClip = null;
@property(cc.AudioClip)
hit5: cc.AudioClip = null;
mAudioMap: { [name: string]: number };
bgMusicVolume: number;
effectMusicVolume: number;
mMusicSwitch: number;
mEffectSwitch: number;
brickSound: any;
reward: boolean;
finish: boolean;
rewardCount: number;
mMusicKey: any;
private musicRequestId: number = 0;
private shouldRestartMusicOnShow: boolean = false;
private restartMusicTimer: number = null;
onLoad() {
if (AudioManager._instance == null) {
AudioManager._instance = this;
cc.fx.AudioManager = AudioManager;
cc.game.addPersistRootNode(this.node);
}
else {
return;
}
this.reward = false;
this.finish = false;
this.rewardCount = 0;
this.ctor();
this.registerAndroidWechatLifecycle();
this.preload();
}
onDestroy() {
this.unregisterAndroidWechatLifecycle();
}
ctor() {
this.mAudioMap = {};
/**
* 默认音量大小
* @type {number}
*/
this.bgMusicVolume = 0.1;
this.effectMusicVolume = 1;
this.mMusicSwitch = 1;
this.mEffectSwitch = 1;
// 初始化远程音频 URL 映射
this.initAudioUrlMap();
}
/**
* 初始化远程音频 URL 映射表
* 将所有音频文件路径映射到 CDN 完整 URL
*/
initAudioUrlMap() {
const base = this.CDN_AUDIO_BASE;
// 音效列表
const names = [
'main_bgm', 'homeScene', 'gameScene',
'xiaochu', 'hit', 'down', 'win',
'anniu_Big', 'anniu_little', 'tanchuang', 'zhuan1', 'zhuan2',
'adhesive', 'freezeBlock', 'freezeDoor', 'hammer',
'lockBlock1', 'lockBlock2', 'lockDoor',
'magic1', 'magic2', 'simpleColor', 'stacking',
'starBlock', 'timePause1', 'timePause2',
'hit1', 'hit2', 'hit3', 'hit4', 'hit5'
];
names.forEach((name) => {
this.audioUrlMap[name] = base + name + '.mp3';
});
}
private isLayerOneVariantB(): boolean {
const gameInfo = cc.fx.GameConfig && cc.fx.GameConfig.GM_INFO;
const abTests = gameInfo && gameInfo.abTests;
return !!abTests && Number(abTests.layer_one) === 2;
}
private isAndroidWechatGame(): boolean {
return cc.sys.platform === cc.sys.WECHAT_GAME
&& cc.sys.os === cc.sys.OS_ANDROID;
}
private logLifecycleAudioError(stage: string, error: any) {
try {
console.error('AudioManager 前后台音乐处理失败:', stage, error);
} catch (e) {
// 日志本身也不能影响前后台事件链。
}
}
private registerAndroidWechatLifecycle() {
try {
if (!this.isAndroidWechatGame()) {
return;
}
} catch (error) {
this.logLifecycleAudioError('注册监听前的平台判断', error);
return;
}
try {
cc.game.on(cc.game.EVENT_HIDE, this.onHide, this);
} catch (error) {
this.logLifecycleAudioError('注册 EVENT_HIDE 监听', error);
}
try {
cc.game.on(cc.game.EVENT_SHOW, this.onShow, this);
} catch (error) {
this.logLifecycleAudioError('注册 EVENT_SHOW 监听', error);
}
}
private unregisterAndroidWechatLifecycle() {
try {
cc.game.off(cc.game.EVENT_HIDE, this.onHide, this);
} catch (error) {
this.logLifecycleAudioError('移除 EVENT_HIDE 监听', error);
}
try {
cc.game.off(cc.game.EVENT_SHOW, this.onShow, this);
} catch (error) {
this.logLifecycleAudioError('移除 EVENT_SHOW 监听', error);
}
if (this.restartMusicTimer != null) {
try {
clearTimeout(this.restartMusicTimer);
} catch (error) {
this.logLifecycleAudioError('清理恢复定时器', error);
}
this.restartMusicTimer = null;
}
}
private isBackgroundMusicName(name: string): boolean {
return name === 'main_bgm'
|| name === 'homeScene'
|| name === 'gameScene';
}
private isBackgroundMusicAllowedForGroup(name: string): boolean {
if (!this.isBackgroundMusicName(name)) {
return true;
}
if (this.isLayerOneVariantB()) {
return name === 'homeScene' || name === 'gameScene';
}
// 未分组、异常值和 layer_one=1 都按默认 A 方案处理。
return name === 'main_bgm';
}
private getCurrentSceneName(sceneName?: string): string {
if (sceneName) {
return sceneName;
}
const scene = cc.director.getScene && cc.director.getScene();
if (scene && scene.name) {
return scene.name;
}
const gameInfo = cc.fx.GameConfig && cc.fx.GameConfig.GM_INFO;
return gameInfo && gameInfo.sceneValue ? gameInfo.sceneValue : '';
}
public getSceneBackgroundMusicName(sceneName?: string): string {
if (!this.isLayerOneVariantB()) {
return 'main_bgm';
}
const currentSceneName = this.getCurrentSceneName(sceneName);
if (currentSceneName === 'HomeScene') {
return 'homeScene';
}
if (currentSceneName === 'GameScene') {
return 'gameScene';
}
return '';
}
/**
* 根据名称获取远程音频 URL
*/
getRemoteUrl(name: string): string {
if (name == null || name == "null") {
return "";
}
return this.audioUrlMap[name] || (this.CDN_AUDIO_BASE + name + '.mp3');
}
/**
* 加载远程音频
* 优先从缓存获取,没有则从 CDN 下载
*/
loadRemoteAudio(name: string, callback?: (clip: cc.AudioClip) => void) {
if (!this.isBackgroundMusicAllowedForGroup(name)) {
console.warn('当前 layer_one 分组禁止加载背景音乐:', name);
callback && callback(null);
return;
}
// 1. 已有缓存直接返回
if (this.audioCache[name]) {
callback && callback(this.audioCache[name]);
return;
}
// 2. 本地有 @property 注入的 AudioClip优先使用
if (this[name] instanceof cc.AudioClip) {
this.audioCache[name] = this[name];
callback && callback(this[name]);
return;
}
// 已有相同音频正在加载时只追加回调,不重复请求 CDN。
if (this.audioLoadingCallbacks[name]) {
callback && this.audioLoadingCallbacks[name].push(callback);
return;
}
this.audioLoadingCallbacks[name] = [];
callback && this.audioLoadingCallbacks[name].push(callback);
const finishLoading = (clip: cc.AudioClip) => {
const callbacks = this.audioLoadingCallbacks[name] || [];
delete this.audioLoadingCallbacks[name];
for (let i = 0; i < callbacks.length; i++) {
callbacks[i](clip);
}
};
// 3. 从远程 CDN 加载
const url = this.getRemoteUrl(name);
// console.log('远程加载音频:', name, url);
if (url == null || url == "") {
finishLoading(null);
return;
}
// 微信小游戏:先查本地缓存,没有再下载
if (cc.sys.platform === cc.sys.WECHAT_GAME) {
// @ts-ignore
if (typeof wx !== 'undefined' && wx.downloadFile) {
this.loadRemoteAudioWithCache(name, url, finishLoading);
return;
}
}
// 4. 其他平台直接 loadRemote
cc.assetManager.loadRemote(url, (err, audioClip: cc.AudioClip) => {
if (err) {
console.error('远程音频加载失败:', name, err);
finishLoading(null);
return;
}
this.audioCache[name] = audioClip;
finishLoading(audioClip);
});
}
/**
* 微信小游戏:优先从本地缓存加载音频,没有则下载并保存到本地
* 缓存路径: wx.env.USER_DATA_PATH/audio_cache/<name>.mp3
* 微信本地缓存总上限 50MB超出 saveFile 会失败但不影响本次播放
*/
loadRemoteAudioWithCache(name: string, url: string, callback?: (clip: cc.AudioClip) => void) {
const localPath = this.getLocalCachePath(name);
if (!localPath) {
// 拿不到本地路径,回退到直接下载
this.downloadAndCacheAudio(name, url, '', callback);
return;
}
// @ts-ignore
const fs = wx.getFileSystemManager();
// 确保缓存目录存在(递归创建)
try {
fs.mkdirSync(this.getLocalCacheDir(), true);
} catch (e) {
// 目录已存在或其他错误,忽略
}
// 1. 检查本地缓存文件是否存在
fs.access({
path: localPath,
success: () => {
// 本地缓存命中,直接从本地路径加载
// console.log('____________音频本地缓存命中:', name, localPath);
cc.assetManager.loadRemote(localPath, (err, audioClip: cc.AudioClip) => {
if (err) {
//console.log('____________本地缓存加载失败重新下载:', name, err);
// 加载失败,删除坏文件并重新下载
try { fs.unlinkSync(localPath); } catch (e) { }
this.downloadAndCacheAudio(name, url, localPath, callback);
return;
}
this.audioCache[name] = audioClip;
//console.log('____________本地缓存加载成功:', name, this.audioCache);
callback && callback(audioClip);
});
},
fail: () => {
// 本地缓存未命中,从 CDN 下载
//console.log('____________本地缓存未命中下载音频:', name, url);
this.downloadAndCacheAudio(name, url, localPath, callback);
}
});
}
/**
* 下载音频,保存到本地后再从本地路径加载(避免 loadRemote 与 saveFile 竞争)
* 关键wx.saveFile 是移动 temp 文件,而 cc.assetManager.loadRemote 是异步读,
* 如果先 loadRemote(tempPath) 再 saveFile引擎真正读 temp 时文件已被移走 → 500。
* 正确顺序saveFile 落盘 → loadRemote(本地路径)
* @param localPath 本地保存路径,为空时只下载不缓存
*/
downloadAndCacheAudio(name: string, url: string, localPath: string, callback?: (clip: cc.AudioClip) => void) {
// @ts-ignore
const fs = wx.getFileSystemManager();
// @ts-ignore
wx.downloadFile({
url: url,
success: (res: any) => {
if (res.statusCode !== 200) {
//console.error('____________下载音频失败:', name, res.statusCode);
callback && callback(null);
return;
}
const tempFilePath = res.tempFilePath;
if (!localPath) {
// 不需要缓存,直接从 temp 加载
cc.assetManager.loadRemote(tempFilePath, (err, audioClip: cc.AudioClip) => {
if (err) {
//console.error('____________远程音频加载失败:', name, err);
callback && callback(null);
return;
}
this.audioCache[name] = audioClip;
callback && callback(audioClip);
});
return;
}
// 先 saveFile 落盘到本地缓存,再从稳定的本地路径 loadRemote
fs.saveFile({
tempFilePath: tempFilePath,
filePath: localPath,
success: (saveRes: any) => {
const savedPath = saveRes.savedFilePath || localPath;
//console.log('____________音频已缓存到本地:', name, savedPath);
cc.assetManager.loadRemote(savedPath, (err, audioClip: cc.AudioClip) => {
if (err) {
console.error('____________本地缓存加载失败:', name, err);
try { fs.unlinkSync(savedPath); } catch (e) { }
callback && callback(null);
return;
}
//console.log('____________远程音频加载成功:', name);
this.audioCache[name] = audioClip;
callback && callback(audioClip);
});
},
fail: (saveErr: any) => {
// saveFile 失败50MB 超出 / temp 已被清理等),回退到 temp 直接加载
//console.log('____________saveFile 失败,回退到 temp 直接加载:', name, saveErr);
cc.assetManager.loadRemote(tempFilePath, (err, audioClip: cc.AudioClip) => {
if (err) {
//console.error('____________远程音频加载失败:', name, err);
callback && callback(null);
return;
}
this.audioCache[name] = audioClip;
callback && callback(audioClip);
});
}
});
},
fail: (err: any) => {
//console.error('____________downloadFile 失败:', name, err);
callback && callback(null);
}
});
}
/**
* 清除所有音频本地缓存(调试/换包用)
*/
clearLocalAudioCache(onComplete?: () => void) {
if (!(cc.sys.platform === cc.sys.WECHAT_GAME)) {
onComplete && onComplete();
return;
}
// @ts-ignore
const fs = wx.getFileSystemManager();
const dir = this.getLocalCacheDir();
if (!dir) {
onComplete && onComplete();
return;
}
fs.readdir({
dirPath: dir,
success: (res: any) => {
const files: string[] = res.files || [];
let removed = 0;
if (files.length === 0) {
onComplete && onComplete();
return;
}
files.forEach((file) => {
const filePath = dir + file;
fs.unlink({
filePath: filePath,
success: () => {
removed++;
if (removed === files.length) onComplete && onComplete();
},
fail: () => {
removed++;
if (removed === files.length) onComplete && onComplete();
}
});
});
},
fail: () => {
onComplete && onComplete();
}
});
}
play(audioSource: cc.AudioClip, loop: boolean, callback: Function, isBgMusic: boolean) {
// if (isBgMusic && !this.mMusicSwitch) return;
// if (!isBgMusic && !this.mEffectSwitch) return;
const shouldPlayAsBackgroundMusic = isBgMusic === true;
var volume = shouldPlayAsBackgroundMusic ? this.bgMusicVolume : this.effectMusicVolume;
// if (cc.sys.isBrowser) {
// if(audioSource == this.brickSound){
// volume = 0.1;
// }
volume = 1;
cc.audioEngine.setEffectsVolume(1);
cc.audioEngine.setMusicVolume(1);
if (shouldPlayAsBackgroundMusic) {
const musicContext = cc.audioEngine.playMusic(audioSource, loop);
this.mAudioMap[audioSource.name] = musicContext;
return audioSource;
}
if (audioSource.name == "lose" || audioSource.name == "zhuan1" || audioSource.name == "zhuan2") {
cc.audioEngine.setEffectsVolume(0.5);
}
else {
cc.audioEngine.setEffectsVolume(1);
}
var context = cc.audioEngine.playEffect(audioSource, loop);
// if (callback) {
// cc.audioEngine.setFinishCallback(context, function () {
// if (this && callback) {
// callback.call(this);
// }
// }.bind(this));
// }
// cc.wwx.OutPut.log('play audio effect isBrowser: ' + context.src);
this.mAudioMap[audioSource.name] = context;
return audioSource;
// } else {
// return audioSource;
// }
}
save() {
// cc.wwx.Storage.setItem(cc.wwx.Storage.Key_Setting_Music_Volume, this.mMusicSwitch);
// cc.wwx.Storage.setItem(cc.wwx.Storage.Key_Setting_Effect_Volume, this.mEffectSwitch);
}
// static get Instance()
// {
// if (this._instance == null)
// {
// this._instance = new AudioManager();
// }
// return this._instance;
// }
preload() {
if (!(cc.sys.platform === cc.sys.WECHAT_GAME || cc.sys.platform === cc.sys.BYTEDANCE_GAME)) { return; }
// 音效全部预加载;背景音乐只预加载当前 AB 分组允许的资源。
const allSounds = Object.keys(this.audioUrlMap).filter((name) => {
return this.isBackgroundMusicAllowedForGroup(name);
});
console.log('AudioManager 开始按 layer_one 分组预加载音频,共', allSounds.length, '个');
this.preloadAudios(
allSounds,
(loaded, total) => {
// console.log('音频预加载进度:', loaded, '/', total);
},
() => {
console.log('所有音频预加载完成');
},
// 当前场景对应的背景音乐加载完成后自动播放。
(name, clip) => {
if (this.isBackgroundMusicName(name)
&& name === this.getSceneBackgroundMusicName()
&& clip) {
if (cc.fx.GameConfig.GM_INFO && cc.fx.GameConfig.GM_INFO.musicOpen) {
console.log(name, '加载完成,音乐开关已开启,自动播放');
this.playSceneMusic();
} else {
console.log(name, '加载完成,但音乐开关未开启');
}
}
}
);
}
/**
* 预加载指定的音频列表
* @param names 音频名称数组
* @param onProgress 进度回调 (loaded, total)
* @param onComplete 完成回调
*/
preloadAudios(
names: string[],
onProgress?: (loaded: number, total: number) => void,
onComplete?: () => void,
onItemLoaded?: (name: string, clip: cc.AudioClip) => void
) {
if (!names || names.length === 0) {
onComplete && onComplete();
return;
}
let loaded = 0;
const total = names.length;
const onOneLoaded = (name: string, clip: cc.AudioClip) => {
loaded++;
onItemLoaded && onItemLoaded(name, clip);
onProgress && onProgress(loaded, total);
if (loaded === total) onComplete && onComplete();
};
names.forEach((name) => {
if (this[name] instanceof cc.AudioClip || this.audioCache[name]) {
onOneLoaded(name, this[name] || this.audioCache[name]);
return;
}
this.loadRemoteAudio(name, (clip) => {
onOneLoaded(name, clip);
});
});
}
getAudioMusicSwitch() {
return this.mMusicSwitch;
}
getAudioEffectSwitch() {
return this.mEffectSwitch;
}
trunAudioSound(on) {
this.switchMusic(on);
this.switchEffect(on)
}
switchMusic(on) {
if (this.mMusicSwitch != (on ? 1 : 0)) {
this.mMusicSwitch = 1 - this.mMusicSwitch;
// this.save();
}
if (on) {
this.playMusicGame();
}
else {
this.stopMusic();
}
}
switchEffect(on) {
if (this.mEffectSwitch != (on ? 1 : 0)) {
this.mEffectSwitch = 1 - this.mEffectSwitch;
// this.save();
}
}
onHide() {
try {
if (!this.isAndroidWechatGame()) {
return;
}
} catch (error) {
this.logLifecycleAudioError('onHide 平台判断', error);
return;
}
try {
const gameInfo = cc.fx
&& cc.fx.GameConfig
&& cc.fx.GameConfig.GM_INFO;
// Android 微信退后台或锁屏后,原音乐上下文可能无法正常 resume。
// 只要音乐开关开启,回前台时就按当时的场景和 AB 分组重新播放。
this.shouldRestartMusicOnShow = !!(gameInfo && gameInfo.musicOpen);
} catch (error) {
this.shouldRestartMusicOnShow = false;
this.logLifecycleAudioError('onHide 读取音乐开关', error);
}
try {
cc.audioEngine.pauseAll();
} catch (error) {
this.logLifecycleAudioError('onHide 暂停音频', error);
}
try {
this.stopMusic();
} catch (error) {
this.logLifecycleAudioError('onHide 停止背景音乐', error);
}
}
onShow() {
try {
if (!this.isAndroidWechatGame()) {
return;
}
} catch (error) {
this.logLifecycleAudioError('onShow 平台判断', error);
return;
}
try {
cc.audioEngine.resumeAll();
} catch (error) {
this.logLifecycleAudioError('onShow 恢复音频', error);
}
if (!this.shouldRestartMusicOnShow) {
return;
}
this.shouldRestartMusicOnShow = false;
if (this.restartMusicTimer != null) {
try {
clearTimeout(this.restartMusicTimer);
} catch (error) {
this.logLifecycleAudioError('onShow 清理旧定时器', error);
}
this.restartMusicTimer = null;
}
try {
// 等微信小游戏从锁屏/后台恢复音频环境后,再创建新的背景音乐播放。
this.restartMusicTimer = setTimeout(() => {
this.restartMusicTimer = null;
try {
const gameInfo = cc.fx
&& cc.fx.GameConfig
&& cc.fx.GameConfig.GM_INFO;
if (!gameInfo || !gameInfo.musicOpen) {
return;
}
const sceneName = this.getCurrentSceneName();
const backgroundMusicName = this.getSceneBackgroundMusicName(sceneName);
if (!backgroundMusicName) {
return;
}
console.log(
'Android 微信小游戏回到前台,重新播放场景背景音乐:',
sceneName,
backgroundMusicName
);
this.stopMusic();
this.playSceneMusic(sceneName);
} catch (error) {
this.logLifecycleAudioError('onShow 重新播放背景音乐', error);
}
}, 300);
} catch (error) {
this.restartMusicTimer = null;
this.logLifecycleAudioError('onShow 创建恢复定时器', error);
}
}
//播放音效
playEffect(name, callback) {
if (!cc.fx.GameConfig.GM_INFO.effectOpen) {
return;
}
if (this.isBackgroundMusicName(name)) {
console.warn('背景音乐必须通过 playSceneMusic/playMusic 播放:', name);
return;
}
// 1. 本地有 AudioClip 直接播放
if (this[name] instanceof cc.AudioClip) {
// console.log('____________本地有音频成功:', name);
return this.play(this[name], false, callback, false);
}
// 2. 缓存中有已加载的 AudioClip
if (this.audioCache[name]) {
// console.log('____________缓存音频加载成功:', name, this.audioCache);
return this.play(this.audioCache[name], false, callback, false);
}
// console.log('____________最终走远程加载:', name);
// 3. 远程加载
this.loadRemoteAudio(name, (clip) => {
if (clip) {
// console.log('____________远程音频加载成功:', name);
this.play(clip, false, callback, false);
} else {
console.log('音效播放失败:', name);
}
});
}
playMusic(key, callback, loop) {
console.log("音频开关________________________________________", cc.fx.GameConfig.GM_INFO.musicOpen);
if (!cc.fx.GameConfig.GM_INFO.musicOpen) {
return;
}
loop = typeof loop == 'undefined' || loop ? true : false;
const targetName = (key instanceof cc.AudioClip) ? key.name : key;
if (this.isBackgroundMusicName(targetName)) {
if (!this.isBackgroundMusicAllowedForGroup(targetName)) {
console.warn('当前 layer_one 分组禁止播放背景音乐:', targetName);
return;
}
const expectedName = this.getSceneBackgroundMusicName();
if (targetName !== expectedName) {
console.warn('当前场景禁止播放背景音乐:', targetName, '期望:', expectedName);
return;
}
}
// 如果请求播放的就是当前音乐,不重复中断和重启。
if (this.mMusicKey != null && this.mMusicKey === targetName
&& cc.audioEngine.isMusicPlaying && cc.audioEngine.isMusicPlaying()) {
console.log("当前已在播放的音乐,跳过 stop+restart,避免打断");
return;
}
// 场景切换时先停止旧背景音乐;请求编号用于拦截旧场景的异步加载回调。
this.stopMusic();
const requestId = ++this.musicRequestId;
const playClip = (clip: cc.AudioClip, name: string) => {
if (!clip || requestId !== this.musicRequestId) {
return;
}
if (this.isBackgroundMusicName(name)
&& name !== this.getSceneBackgroundMusicName()) {
return;
}
this.play(clip, loop, callback, true);
this.mMusicKey = name;
};
// key 可能是 AudioClip 实例,也可能是字符串名称
if (key instanceof cc.AudioClip) {
playClip(key, targetName);
console.log("开始播放音乐", key.name);
return;
}
// 字符串名称:尝试从缓存或远程加载
const name = key;
console.log("开始播放音乐的名称", name);
if (this.audioCache[name]) {
console.log("从缓存播放音乐", name);
playClip(this.audioCache[name], name);
} else {
this.loadRemoteAudio(name, (clip) => {
console.log("从远程加载音乐", name, clip);
if (clip) {
playClip(clip, name);
}
});
}
}
public playSceneMusic(sceneName?: string) {
const backgroundMusicName = this.getSceneBackgroundMusicName(sceneName);
if (!backgroundMusicName) {
this.stopMusic();
return;
}
this.playMusic(backgroundMusicName, null, true);
}
/**
* 兼容原有调用入口,根据当前场景和 layer_one 自动选择背景音乐。
*/
playMusicGame() {
this.playSceneMusic();
}
/**
* 停止背景音乐播放
*/
stopMusic() {
console.log("无论什么原因——————————————暂停了背景音乐播放", this.mMusicKey);
this.musicRequestId++;
this.mMusicKey = null;
cc.audioEngine.stopMusic();
}
/**
* 恢复被暂停的背景音乐播放
*/
resumeMusic() {
// 调用 cc.audioEngine 的 resumeMusic 方法恢复音乐播放
cc.audioEngine.resumeMusic();
}
/*
* 游戏开始音效
*
*/
playGameStart() {
}
/*
* 失败的游戏结束
*/
playGameOver() {
}
/*
* 成功的游戏结束
*/
playGameResultFailed() {
}
/*
* 成功的游戏结束
*/
playGameResultSuccess() {
}
/**
* 报警的音效
*/
/**
* 按钮
*/
playAudioButton() {
// return this.play(this.audioButtonClick, false,null,this.mEffectSwitch);
}
};
// export { AudioManager };