first commit
This commit is contained in:
30
assets/seripts/libs/AudioEffect.ts
Normal file
30
assets/seripts/libs/AudioEffect.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { AudioManager } from "./AudioManager";
|
||||
|
||||
//音效管理类
|
||||
export default class AudioEffect {
|
||||
//播放通用的音效
|
||||
static playCommonAudio(audioUrl: string): void {
|
||||
if (!audioUrl) {
|
||||
return;
|
||||
}
|
||||
//播放音效
|
||||
AudioManager.inst.playOneShot(audioUrl);
|
||||
}
|
||||
//播放点击音效
|
||||
public static playClickAudio() {
|
||||
let audioUrl = "audios/common/click";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
|
||||
//播放撒金币的音效
|
||||
public static playGoldAudio() {
|
||||
let audioUrl = "audios/common/gold";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
|
||||
//播放金币入账的音效
|
||||
public static playCoinsEntryAudio() {
|
||||
let audioUrl = "audios/common/coins_entry";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/AudioEffect.ts.meta
Normal file
9
assets/seripts/libs/AudioEffect.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "444d38fc-633e-43bb-824b-192c2ee2730e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
110
assets/seripts/libs/AudioManager.ts
Normal file
110
assets/seripts/libs/AudioManager.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { Node, AudioSource, AudioClip, resources, director } from "cc";
|
||||
/**
|
||||
* @en
|
||||
* this is a sington class for audio play, can be easily called from anywhere in you project.
|
||||
* @zh
|
||||
* 这是一个用于播放音频的单件类,可以很方便地在项目的任何地方调用。
|
||||
*/
|
||||
export class AudioManager {
|
||||
private static _inst: AudioManager;
|
||||
public static get inst(): AudioManager {
|
||||
if (this._inst == null) {
|
||||
this._inst = new AudioManager();
|
||||
}
|
||||
return this._inst;
|
||||
}
|
||||
private _audioSource: AudioSource;
|
||||
constructor() {
|
||||
//@en create a node as audioMgr
|
||||
//@zh 创建一个节点作为 audioMgr
|
||||
let audioMgr = new Node();
|
||||
audioMgr.name = "__audioMgr__";
|
||||
|
||||
//@en add to the scene.
|
||||
//@zh 添加节点到场景
|
||||
director.getScene().addChild(audioMgr);
|
||||
|
||||
//@en make it as a persistent node, so it won't be destroied when scene change.
|
||||
//@zh 标记为常驻节点,这样场景切换的时候就不会被销毁了
|
||||
director.addPersistRootNode(audioMgr);
|
||||
|
||||
//@en add AudioSource componrnt to play audios.
|
||||
//@zh 添加 AudioSource 组件,用于播放音频。
|
||||
this._audioSource = audioMgr.addComponent(AudioSource);
|
||||
}
|
||||
|
||||
public get audioSource() {
|
||||
return this._audioSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @en
|
||||
* play short audio, such as strikes,explosions
|
||||
* @zh
|
||||
* 播放短音频,比如 打击音效,爆炸音效等
|
||||
* @param sound clip or url for the audio
|
||||
* @param volume
|
||||
*/
|
||||
playOneShot(sound: AudioClip | string, volume: number = 1.0) {
|
||||
if (sound instanceof AudioClip) {
|
||||
this._audioSource.playOneShot(sound, volume);
|
||||
} else {
|
||||
resources.load(sound, (err, clip: AudioClip) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
this._audioSource.playOneShot(clip, volume);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @en
|
||||
* play long audio, such as the bg music
|
||||
* @zh
|
||||
* 播放长音频,比如 背景音乐
|
||||
* @param sound clip or url for the sound
|
||||
* @param volume
|
||||
*/
|
||||
play(sound: AudioClip | string, volume: number = 1.0) {
|
||||
if (sound instanceof AudioClip) {
|
||||
this._audioSource.stop();
|
||||
this._audioSource.clip = sound;
|
||||
this._audioSource.play();
|
||||
this.audioSource.volume = volume;
|
||||
} else {
|
||||
resources.load(sound, (err, clip: AudioClip) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
this._audioSource.stop();
|
||||
this._audioSource.clip = clip;
|
||||
this._audioSource.play();
|
||||
this.audioSource.volume = volume;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the audio play
|
||||
*/
|
||||
stop() {
|
||||
this._audioSource.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* pause the audio play
|
||||
*/
|
||||
pause() {
|
||||
this._audioSource.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* resume the audio play
|
||||
*/
|
||||
resume() {
|
||||
this._audioSource.play();
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/AudioManager.ts.meta
Normal file
9
assets/seripts/libs/AudioManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "3b9035fb-99e0-4028-9bc0-84149d0ba3bf",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
48
assets/seripts/libs/EventDispatcher.ts
Normal file
48
assets/seripts/libs/EventDispatcher.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { _decorator, Component, EventTarget } from "cc";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
const event_target = new EventTarget();
|
||||
|
||||
export class EventDispatcher {
|
||||
private static data: EventDispatcher;
|
||||
|
||||
// 小恐龙吃到音符事件
|
||||
public static readonly DINO_EAT_NOTE = "dino_eat_note";
|
||||
// 音符碰到线事件
|
||||
public static readonly UPDATE_LIFE = "update_life";
|
||||
// 开始游戏事件
|
||||
public static readonly GAME_STAET = "game_start";
|
||||
|
||||
// 游戏结束事件
|
||||
public static readonly GAME_OVER = "game_over";
|
||||
// 游戏闯关成功事件
|
||||
public static readonly PASS_LEVLE = "pass_levle";
|
||||
|
||||
// 暂停事件
|
||||
public static readonly GAME_PAUSE = "game_pause";
|
||||
// 继续游戏事件
|
||||
public static readonly GAME_RESUME = "game_resume";
|
||||
// 清除所有音符事件
|
||||
public static readonly CLEAR_ALL_NOTE = "clear_all_note";
|
||||
// 清楚可视区域的音符事件
|
||||
public static readonly CLEAR_SCREEN_NOTE = "clear_screen_note";
|
||||
// 移动小恐龙到线的位置事件
|
||||
public static readonly MOVE_DINO_TO_LINE = "move_dino_to_line";
|
||||
// 显示结果弹窗事件
|
||||
public static readonly SHOW_RESULT_MODAL = "show_result_modal";
|
||||
// 显示暂停弹窗事件
|
||||
public static readonly SHOW_PAUSE_MODAL = "show_pause_modal";
|
||||
// 显示继续游戏弹窗事件
|
||||
public static readonly SHOW_CONTINUE_MODAL = "show_continue_modal";
|
||||
|
||||
static getTarget() {
|
||||
if (EventDispatcher.data == null) {
|
||||
EventDispatcher.data = new EventDispatcher();
|
||||
}
|
||||
return EventDispatcher.data.getEventTarget();
|
||||
}
|
||||
|
||||
private getEventTarget(): EventTarget {
|
||||
return event_target;
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/EventDispatcher.ts.meta
Normal file
9
assets/seripts/libs/EventDispatcher.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "aa122590-0731-48e3-890b-886796d95b60",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
506
assets/seripts/libs/SpeedAudioPlayer.ts
Normal file
506
assets/seripts/libs/SpeedAudioPlayer.ts
Normal file
@@ -0,0 +1,506 @@
|
||||
import { _decorator, AudioClip, sys } from "cc";
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
// 声明微信小游戏全局变量(避免 TypeScript 报错)
|
||||
declare const wx: any;
|
||||
|
||||
@ccclass("SpeedAudioPlayer")
|
||||
export class SpeedAudioPlayer {
|
||||
// H5 平台核心对象
|
||||
private audioElement: HTMLAudioElement | null = null;
|
||||
// 微信小游戏平台核心对象
|
||||
private innerAudioContext: any = null;
|
||||
// 跨平台统一播放状态
|
||||
private currentPlayRate: number = 1.0;
|
||||
private volume: number = 1.0;
|
||||
private isPlaying: boolean = false;
|
||||
private isPaused: boolean = false;
|
||||
// 跨平台统一状态缓存(切换音频/恢复播放复用)
|
||||
private lastPlayState: { rate: number; position: number } = {
|
||||
rate: 1.0,
|
||||
position: 0,
|
||||
};
|
||||
// 缓存当前音频剪辑
|
||||
private currentAudioClip: AudioClip | null = null;
|
||||
|
||||
/**
|
||||
* 初始化音频(跨平台自动适配)
|
||||
* @param audioClip Cocos 音频剪辑
|
||||
*/
|
||||
async init(audioClip: AudioClip) {
|
||||
if (!audioClip) {
|
||||
console.error("SpeedAudioPlayer: 音频剪辑不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前音频剪辑
|
||||
this.currentAudioClip = audioClip;
|
||||
|
||||
// 1. H5 平台(浏览器/支持 DOM 的环境)
|
||||
if (this.isH5Platform()) {
|
||||
await this.initH5(audioClip);
|
||||
}
|
||||
// 2. 微信小游戏平台(原生 API 环境)
|
||||
else if (this.isWxGamePlatform()) {
|
||||
await this.initWxGame(audioClip);
|
||||
}
|
||||
// 3. 不支持的平台
|
||||
else {
|
||||
console.error("SpeedAudioPlayer: 暂不支持当前运行平台");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台核心:切换音频文件(保留/重置状态)
|
||||
* @param newAudioClip 新的音频剪辑
|
||||
* @param keepState 是否保留原播放状态(速率/暂停位置),默认true
|
||||
*/
|
||||
async changeAudioClip(newAudioClip: AudioClip, keepState: boolean = true) {
|
||||
if (!newAudioClip) {
|
||||
console.error("SpeedAudioPlayer: 切换音频失败,音频剪辑不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 记录当前状态(跨平台统一)
|
||||
if (keepState) {
|
||||
this.lastPlayState.rate = this.currentPlayRate;
|
||||
this.lastPlayState.position = this.getCurrentPlayTime();
|
||||
} else {
|
||||
this.lastPlayState = { rate: 1.0, position: 0 };
|
||||
}
|
||||
|
||||
// 2. 停止当前音频(跨平台统一)
|
||||
this.stop();
|
||||
|
||||
// 3. 初始化新音频(自动适配平台)
|
||||
await this.init(newAudioClip);
|
||||
|
||||
// 4. 恢复播放状态(跨平台统一)
|
||||
if (keepState) {
|
||||
this.currentPlayRate = this.lastPlayState.rate;
|
||||
this.setPlayRate(this.currentPlayRate); // 同步速率到当前平台
|
||||
|
||||
// 定位到缓存位置,根据原状态恢复播放/暂停
|
||||
if (this.isPaused) {
|
||||
this.seek(this.lastPlayState.position);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 切换音频后保留暂停状态,位置:${this.lastPlayState.position.toFixed(
|
||||
3,
|
||||
)}秒`,
|
||||
);
|
||||
} else if (this.isPlaying) {
|
||||
this.play(this.currentPlayRate, this.lastPlayState.position);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("SpeedAudioPlayer: 音频文件切换成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音频(跨平台统一接口,支持指定速率/起始位置)
|
||||
* @param playRate 播放速率(0.5~2.0)
|
||||
* @param startAt 起始位置(秒,默认0)
|
||||
*/
|
||||
play(playRate: number = 1.0, startAt: number = 0) {
|
||||
// 限制参数范围
|
||||
this.currentPlayRate = Math.max(0.5, Math.min(playRate, 2.0));
|
||||
startAt = Math.max(0, Math.min(startAt, this.getTotalDuration()));
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
this.audioElement.currentTime = startAt;
|
||||
this.audioElement
|
||||
.play()
|
||||
.then(() => {
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: H5 播放开始,速率:${
|
||||
this.currentPlayRate
|
||||
},起始位置:${startAt.toFixed(3)}秒`,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("SpeedAudioPlayer: H5 播放失败(需用户交互触发):", e);
|
||||
});
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.seek(startAt);
|
||||
this.innerAudioContext.play();
|
||||
this.updatePlayState(true, false);
|
||||
this.lastPlayState.position = startAt;
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏播放开始,速率:${
|
||||
this.currentPlayRate
|
||||
},起始位置:${startAt.toFixed(3)}秒`,
|
||||
);
|
||||
}
|
||||
// 未初始化
|
||||
else {
|
||||
console.warn("SpeedAudioPlayer: 音频未初始化完成,无法播放");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停播放(跨平台统一接口,精准保留当前位置)
|
||||
*/
|
||||
pause() {
|
||||
if (!this.isPlaying || this.isPaused) {
|
||||
console.warn("SpeedAudioPlayer: 音频未播放或已暂停,无法暂停");
|
||||
return;
|
||||
}
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
const pausePos = this.audioElement.currentTime;
|
||||
this.cachePausePosition(pausePos);
|
||||
this.updatePlayState(false, true);
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.pause();
|
||||
const pausePos = this.innerAudioContext.currentTime;
|
||||
this.cachePausePosition(pausePos);
|
||||
this.updatePlayState(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复播放(跨平台统一接口,从暂停位置继续)
|
||||
*/
|
||||
resume() {
|
||||
if (!this.isPaused) {
|
||||
console.warn("SpeedAudioPlayer: 音频未暂停,无法恢复播放");
|
||||
return;
|
||||
}
|
||||
|
||||
const resumePos = this.lastPlayState.position;
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.currentTime = resumePos;
|
||||
this.audioElement
|
||||
.play()
|
||||
.then(() => {
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: H5 恢复播放,从${resumePos.toFixed(3)}秒开始`,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("SpeedAudioPlayer: H5 恢复播放失败:", e);
|
||||
});
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
// 恢复播放前确保应用当前设置的速率
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.seek(resumePos);
|
||||
this.innerAudioContext.play();
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏恢复播放,速率:${
|
||||
this.currentPlayRate
|
||||
},从${resumePos.toFixed(3)}秒开始`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止播放(跨平台统一接口,重置位置到开头)
|
||||
*/
|
||||
stop() {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
this.audioElement.currentTime = 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.pause();
|
||||
this.innerAudioContext.seek(0);
|
||||
}
|
||||
|
||||
// 跨平台统一重置状态
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: 停止播放,状态已重置");
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态调节播放速率(跨平台统一接口,播放中实时生效)
|
||||
* @param playRate 新速率
|
||||
*/
|
||||
setPlayRate(playRate: number) {
|
||||
this.currentPlayRate = Math.max(0.5, Math.min(playRate, 2.0));
|
||||
this.lastPlayState.rate = this.currentPlayRate;
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
|
||||
// 微信小游戏真机环境下,设置playbackRate后需要重启播放才能生效
|
||||
// 保存当前播放位置,暂停后重新播放
|
||||
if (this.isPlaying) {
|
||||
const currentPosition = this.getCurrentPlayTime();
|
||||
this.innerAudioContext.pause();
|
||||
this.innerAudioContext.seek(currentPosition);
|
||||
this.innerAudioContext.play();
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏重启播放以应用新速率,当前位置:${currentPosition.toFixed(
|
||||
3,
|
||||
)}秒`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`SpeedAudioPlayer: 速率已调整为:${this.currentPlayRate}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置音量(跨平台统一接口,0~1)
|
||||
* @param volume 音量值
|
||||
*/
|
||||
setVolume(volume: number) {
|
||||
this.volume = Math.max(0, Math.min(volume, 1));
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.volume = this.volume;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
}
|
||||
|
||||
console.log(`SpeedAudioPlayer: 音量已设置为:${this.volume}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频总时长(跨平台统一接口)
|
||||
*/
|
||||
getTotalDuration(): number {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
return this.audioElement.duration || 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
return this.innerAudioContext.duration || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放位置(跨平台统一接口,秒)
|
||||
*/
|
||||
getCurrentPlayTime(): number {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
return this.audioElement.currentTime || 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
return this.innerAudioContext.currentTime || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放状态(跨平台统一接口)
|
||||
*/
|
||||
getPlayState() {
|
||||
return {
|
||||
isPlaying: this.isPlaying,
|
||||
isPaused: this.isPaused,
|
||||
currentPlayRate: this.currentPlayRate,
|
||||
currentPlayTime: this.getCurrentPlayTime(),
|
||||
totalDuration: this.getTotalDuration(),
|
||||
volume: this.volume,
|
||||
platform: this.getCurrentPlatform(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁资源(跨平台统一接口,释放内存)
|
||||
*/
|
||||
destroy() {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.stop();
|
||||
this.audioElement.remove();
|
||||
this.audioElement = null;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.stop();
|
||||
this.innerAudioContext.offCanplay();
|
||||
this.innerAudioContext.offEnded();
|
||||
this.innerAudioContext.offError();
|
||||
this.innerAudioContext.destroy();
|
||||
this.innerAudioContext = null;
|
||||
}
|
||||
|
||||
this.currentAudioClip = null;
|
||||
console.log(`SpeedAudioPlayer: ${this.getCurrentPlatform()} 资源已销毁`);
|
||||
}
|
||||
|
||||
// ---------------------- 内部辅助方法(跨平台适配核心)----------------------
|
||||
/**
|
||||
* 初始化 H5 平台音频(HTML5 Audio)
|
||||
*/
|
||||
private async initH5(audioClip: AudioClip) {
|
||||
// 首次创建 Audio 元素
|
||||
if (!this.audioElement) {
|
||||
this.audioElement = document.createElement("audio");
|
||||
this.audioElement.preload = "auto";
|
||||
// 监听播放结束
|
||||
this.audioElement.onended = () => {
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: H5 音频播放结束");
|
||||
};
|
||||
}
|
||||
|
||||
// 设置音频源并等待加载
|
||||
this.audioElement.src = audioClip.nativeUrl;
|
||||
await new Promise((resolve, reject) => {
|
||||
const onCanplayCallback = () => {
|
||||
this.audioElement!.removeEventListener(
|
||||
"canplaythrough",
|
||||
onCanplayCallback,
|
||||
);
|
||||
resolve(true);
|
||||
};
|
||||
this.audioElement!.addEventListener("canplaythrough", onCanplayCallback);
|
||||
this.audioElement!.addEventListener("error", (e) => {
|
||||
reject(new Error(`H5 音频加载失败:${e}`));
|
||||
});
|
||||
});
|
||||
|
||||
// 恢复缓存状态
|
||||
this.audioElement.volume = this.volume;
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
console.log(
|
||||
"SpeedAudioPlayer: H5 音频初始化成功,总时长:",
|
||||
this.getTotalDuration().toFixed(2),
|
||||
"秒",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 微信小游戏 平台音频(wx.createInnerAudioContext)
|
||||
*/
|
||||
private async initWxGame(audioClip: AudioClip) {
|
||||
// 首次创建微信原生音频上下文
|
||||
if (!this.innerAudioContext) {
|
||||
this.innerAudioContext = wx.createInnerAudioContext();
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.loop = false;
|
||||
|
||||
// 监听原生事件
|
||||
this.innerAudioContext.onCanplay(() => {
|
||||
console.log(
|
||||
"SpeedAudioPlayer: 微信小游戏音频加载完成,总时长:",
|
||||
this.getTotalDuration().toFixed(2),
|
||||
"秒",
|
||||
);
|
||||
});
|
||||
|
||||
this.innerAudioContext.onEnded(() => {
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: 微信小游戏音频播放结束");
|
||||
});
|
||||
|
||||
this.innerAudioContext.onError((err: any) => {
|
||||
console.error("SpeedAudioPlayer: 微信小游戏音频发生错误:", err);
|
||||
this.updatePlayState(false, false);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置音频源并等待加载(核心修复:移除 onCanplayOnce,改用 onCanplay + 手动解除监听)
|
||||
this.innerAudioContext.src = audioClip.nativeUrl;
|
||||
await new Promise((resolve) => {
|
||||
// 定义一次性监听函数
|
||||
const onCanplayCallback = () => {
|
||||
// 手动移除监听,实现「一次性触发」的效果,避免重复回调
|
||||
this.innerAudioContext.offCanplay(onCanplayCallback);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
// 先判断音频是否已加载完成
|
||||
if (this.innerAudioContext.readyState === "canplay") {
|
||||
resolve(true);
|
||||
} else {
|
||||
// 绑定普通 onCanplay 监听,触发后手动移除,替代不存在的 onCanplayOnce
|
||||
this.innerAudioContext.onCanplay(onCanplayCallback);
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复缓存状态
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 H5 平台(浏览器/支持 DOM 的环境)
|
||||
*/
|
||||
private isH5Platform(): boolean {
|
||||
return sys.isBrowser && sys.platform !== sys.Platform.WECHAT_GAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 微信小游戏 平台
|
||||
*/
|
||||
private isWxGamePlatform(): boolean {
|
||||
return sys.platform === sys.Platform.WECHAT_GAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前平台名称(用于日志/调试)
|
||||
*/
|
||||
private getCurrentPlatform(): string {
|
||||
if (this.isH5Platform()) return "H5";
|
||||
if (this.isWxGamePlatform()) return "微信小游戏";
|
||||
return "未知平台";
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一更新播放状态
|
||||
*/
|
||||
private updatePlayState(isPlaying: boolean, isPaused: boolean) {
|
||||
this.isPlaying = isPlaying;
|
||||
this.isPaused = isPaused;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一缓存暂停位置
|
||||
*/
|
||||
private cachePausePosition(position: number) {
|
||||
const clampPos = Math.max(0, Math.min(position, this.getTotalDuration()));
|
||||
this.lastPlayState.position = clampPos;
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 暂停成功,当前位置:${clampPos.toFixed(3)}秒`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一定位音频位置
|
||||
*/
|
||||
private seek(position: number) {
|
||||
const clampPos = Math.max(0, Math.min(position, this.getTotalDuration()));
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.currentTime = clampPos;
|
||||
} else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.seek(clampPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/SpeedAudioPlayer.ts.meta
Normal file
9
assets/seripts/libs/SpeedAudioPlayer.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "15d63406-5748-40e4-a743-a45bc228d712",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
34
assets/seripts/libs/Utils.ts
Normal file
34
assets/seripts/libs/Utils.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { sys } from "cc";
|
||||
|
||||
export default class Utils {
|
||||
/**
|
||||
*
|
||||
* @param {缓存key} key
|
||||
* @param {需要存储的缓存值} value
|
||||
* @param {过期时间} expire
|
||||
*/
|
||||
static setCache(key: string, value: any, expire: number = 0): void {
|
||||
let obj = {
|
||||
data: value, //存储的数据
|
||||
time: Date.now() / 1000, //记录存储的时间戳
|
||||
expire: expire, //记录过期时间,单位秒
|
||||
};
|
||||
sys.localStorage.setItem(key, JSON.stringify(obj));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {缓存key} key
|
||||
*/
|
||||
static getCache(key: string): any {
|
||||
let val: any = sys.localStorage.getItem(key);
|
||||
if (!val) {
|
||||
return null;
|
||||
}
|
||||
val = JSON.parse(val);
|
||||
if (val.expire && Date.now() / 1000 - val.time > val.expire) {
|
||||
sys.localStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return val.data;
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/Utils.ts.meta
Normal file
9
assets/seripts/libs/Utils.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ee787eae-f32a-4d63-9fa4-b609ca06be8e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user