mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +08:00 
			
		
		
		
	播放音效
This commit is contained in:
		
							parent
							
								
									069ee0613b
								
							
						
					
					
						commit
						bdaf37cb0c
					
				
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							@ -27,22 +27,22 @@
 | 
			
		||||
    "vue": "^3.3.4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/cli": "^7.21.5",
 | 
			
		||||
    "@babel/core": "^7.21.8",
 | 
			
		||||
    "@babel/preset-env": "^7.21.5",
 | 
			
		||||
    "@babel/cli": "^7.22.5",
 | 
			
		||||
    "@babel/core": "^7.22.5",
 | 
			
		||||
    "@babel/preset-env": "^7.22.5",
 | 
			
		||||
    "@rollup/plugin-babel": "^6.0.3",
 | 
			
		||||
    "@rollup/plugin-commonjs": "^25.0.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.0.2",
 | 
			
		||||
    "@rollup/plugin-commonjs": "^25.0.1",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.1.0",
 | 
			
		||||
    "@rollup/plugin-replace": "^5.0.2",
 | 
			
		||||
    "@rollup/plugin-terser": "^0.4.3",
 | 
			
		||||
    "@rollup/plugin-typescript": "^11.1.1",
 | 
			
		||||
    "@types/babel__core": "^7.20.0",
 | 
			
		||||
    "@types/babel__core": "^7.20.1",
 | 
			
		||||
    "@types/fontmin": "^0.9.0",
 | 
			
		||||
    "@types/fs-extra": "^9.0.13",
 | 
			
		||||
    "@types/lodash-es": "^4.17.7",
 | 
			
		||||
    "@types/node": "^18.16.14",
 | 
			
		||||
    "@types/ws": "^8.5.4",
 | 
			
		||||
    "@vitejs/plugin-legacy": "^4.0.3",
 | 
			
		||||
    "@types/node": "^18.16.18",
 | 
			
		||||
    "@types/ws": "^8.5.5",
 | 
			
		||||
    "@vitejs/plugin-legacy": "^4.0.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.3",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.1",
 | 
			
		||||
    "chokidar": "^3.5.3",
 | 
			
		||||
@ -51,12 +51,12 @@
 | 
			
		||||
    "form-data": "^4.0.0",
 | 
			
		||||
    "fs-extra": "^10.1.0",
 | 
			
		||||
    "less": "^4.1.3",
 | 
			
		||||
    "rollup": "^3.23.0",
 | 
			
		||||
    "terser": "^5.17.6",
 | 
			
		||||
    "rollup": "^3.25.1",
 | 
			
		||||
    "terser": "^5.18.0",
 | 
			
		||||
    "ts-node": "^10.9.1",
 | 
			
		||||
    "typescript": "^4.9.5",
 | 
			
		||||
    "typescript": "^5.1.3",
 | 
			
		||||
    "unplugin-vue-components": "^0.22.12",
 | 
			
		||||
    "vite": "^4.3.8",
 | 
			
		||||
    "vite": "^4.3.9",
 | 
			
		||||
    "vue-tsc": "^1.6.5",
 | 
			
		||||
    "ws": "^8.13.0"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1992
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
							
						
						
									
										1992
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -245,7 +245,7 @@ main.prototype.init = async function (mode, callback) {
 | 
			
		||||
        const b = {};
 | 
			
		||||
        new Proxy(a, b);
 | 
			
		||||
        new Promise(res => res());
 | 
			
		||||
        eval('`${123}`');
 | 
			
		||||
        eval('`${0}`');
 | 
			
		||||
    } catch {
 | 
			
		||||
        alert('浏览器版本过低,无法游玩本塔!');
 | 
			
		||||
        alert('建议使用Edge浏览器或Chrome浏览器游玩!');
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								src/core/audio/audio.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/core/audio/audio.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
 | 
			
		||||
 | 
			
		||||
const ac = new AudioContext();
 | 
			
		||||
 | 
			
		||||
interface BaseNode {
 | 
			
		||||
    node: AudioNode;
 | 
			
		||||
    channel?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface AudioPlayerEvent extends EmitableEvent {
 | 
			
		||||
    play: (node: AudioBufferSourceNode) => void;
 | 
			
		||||
    update: (audio: AudioBuffer) => void;
 | 
			
		||||
    end: (node: AudioBufferSourceNode) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AudioPlayer extends EventEmitter<AudioPlayerEvent> {
 | 
			
		||||
    static ac: AudioContext = ac;
 | 
			
		||||
    static index: number = 0;
 | 
			
		||||
 | 
			
		||||
    /** 音频的索引,这样的话可以复用来提高性能表现 */
 | 
			
		||||
    index: number = AudioPlayer.index++;
 | 
			
		||||
 | 
			
		||||
    data: ArrayBuffer;
 | 
			
		||||
    buffer: AudioBuffer | null = null;
 | 
			
		||||
    source?: AudioBufferSourceNode;
 | 
			
		||||
 | 
			
		||||
    baseNode: BaseNode[] = [];
 | 
			
		||||
 | 
			
		||||
    constructor(data: ArrayBuffer) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.data = data;
 | 
			
		||||
        this.update(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新音频数据
 | 
			
		||||
     * @param data 音频的ArrayBuffer数据
 | 
			
		||||
     */
 | 
			
		||||
    async update(data: ArrayBuffer) {
 | 
			
		||||
        this.data = data;
 | 
			
		||||
        this.buffer = await ac.decodeAudioData(data);
 | 
			
		||||
        this.emit('update', this.buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取音频源数据节点
 | 
			
		||||
     */
 | 
			
		||||
    getSource() {
 | 
			
		||||
        this.source ??= ac.createBufferSource();
 | 
			
		||||
        this.source.buffer = this.buffer;
 | 
			
		||||
 | 
			
		||||
        return this.source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 播放音频
 | 
			
		||||
     */
 | 
			
		||||
    play(when?: number, offset?: number, duration?: number) {
 | 
			
		||||
        if (!this.source) return;
 | 
			
		||||
        this.ready();
 | 
			
		||||
        this.source?.start(when, offset, duration);
 | 
			
		||||
 | 
			
		||||
        const source = this.source;
 | 
			
		||||
        this.source?.addEventListener('ended', ev => {
 | 
			
		||||
            this.emit('end', source);
 | 
			
		||||
        });
 | 
			
		||||
        this.emit('play', source);
 | 
			
		||||
 | 
			
		||||
        delete this.source;
 | 
			
		||||
        return source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 准备音频资源连接
 | 
			
		||||
     */
 | 
			
		||||
    ready() {
 | 
			
		||||
        const source = this.getSource();
 | 
			
		||||
        this.baseNode.forEach(v => {
 | 
			
		||||
            source.connect(v.node, 0, v.channel);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取音频输出destination
 | 
			
		||||
     */
 | 
			
		||||
    getDestination() {
 | 
			
		||||
        return ac.destination;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,137 @@
 | 
			
		||||
export class SoundEffect {}
 | 
			
		||||
import { has } from '../../plugin/utils';
 | 
			
		||||
import { AudioPlayer } from './audio';
 | 
			
		||||
 | 
			
		||||
export class SoundEffect extends AudioPlayer {
 | 
			
		||||
    static playIndex = 0;
 | 
			
		||||
 | 
			
		||||
    private playing: Record<string, AudioBufferSourceNode> = {};
 | 
			
		||||
    private _stopingAll: boolean = false;
 | 
			
		||||
 | 
			
		||||
    constructor(data: ArrayBuffer, stereo: boolean = false) {
 | 
			
		||||
        super(data);
 | 
			
		||||
 | 
			
		||||
        this.on('end', node => {
 | 
			
		||||
            if (this._stopingAll) return;
 | 
			
		||||
            const entry = Object.entries(this.playing);
 | 
			
		||||
            const index = entry.find(v => node === v[1])?.[0];
 | 
			
		||||
            if (!index) return;
 | 
			
		||||
            delete this.playing[index];
 | 
			
		||||
        });
 | 
			
		||||
        this.initAudio(stereo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置音频路由线路
 | 
			
		||||
     * @param stereo 是否启用立体声
 | 
			
		||||
     */
 | 
			
		||||
    protected initAudio(stereo: boolean = false) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 播放音频
 | 
			
		||||
     * @returns 音频的唯一id
 | 
			
		||||
     */
 | 
			
		||||
    playSE() {
 | 
			
		||||
        const node = this.play();
 | 
			
		||||
        if (!node) return;
 | 
			
		||||
        const index = SoundEffect.playIndex++;
 | 
			
		||||
        this.playing[index] = node;
 | 
			
		||||
        return index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止所有音频
 | 
			
		||||
     */
 | 
			
		||||
    stopAll() {
 | 
			
		||||
        this._stopingAll = true;
 | 
			
		||||
        Object.values(this.playing).forEach(v => {
 | 
			
		||||
            v.stop();
 | 
			
		||||
        });
 | 
			
		||||
        this.playing = {};
 | 
			
		||||
        this._stopingAll = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据唯一id停止音频
 | 
			
		||||
     * @param index 音频唯一id
 | 
			
		||||
     */
 | 
			
		||||
    stopByIndex(index: number) {
 | 
			
		||||
        this.playing[index]?.stop();
 | 
			
		||||
        delete this.playing[index];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SoundController {
 | 
			
		||||
    add(uri: string, data: ArrayBuffer) {}
 | 
			
		||||
    list: Record<string, SoundEffect> = {};
 | 
			
		||||
 | 
			
		||||
    private seIndex: Record<number, SoundEffect> = {};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加一个新的音频
 | 
			
		||||
     * @param uri 音频的uri
 | 
			
		||||
     * @param data 音频的ArrayBuffer信息,会被解析为AudioBuffer
 | 
			
		||||
     */
 | 
			
		||||
    add(uri: string, data: ArrayBuffer) {
 | 
			
		||||
        const se = new SoundEffect(data);
 | 
			
		||||
        if (this.list[uri]) {
 | 
			
		||||
            console.warn(`Repeated sound effect: ${uri}.`);
 | 
			
		||||
        }
 | 
			
		||||
        return (this.list[uri] = se);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 移除一个音频
 | 
			
		||||
     * @param uri 要移除的音频的uri
 | 
			
		||||
     */
 | 
			
		||||
    remove(uri: string) {
 | 
			
		||||
        delete this.list[uri];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 播放音频
 | 
			
		||||
     * @param sound 音效的名称
 | 
			
		||||
     * @returns 本次播放的音效的唯一标识符,如果音效不存在返回-1
 | 
			
		||||
     */
 | 
			
		||||
    play(sound: SoundIds): number {
 | 
			
		||||
        const se = this.get(sound);
 | 
			
		||||
        const index = se.playSE();
 | 
			
		||||
        if (!has(index)) return -1;
 | 
			
		||||
        this.seIndex[index] = se;
 | 
			
		||||
 | 
			
		||||
        return index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止一个音效的播放
 | 
			
		||||
     * @param id 音效的唯一标识符
 | 
			
		||||
     */
 | 
			
		||||
    stop(id: number) {
 | 
			
		||||
        const se = this.seIndex[id];
 | 
			
		||||
        se.stopByIndex(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止一个名称的所有音效的播放
 | 
			
		||||
     * @param id 音效名称
 | 
			
		||||
     */
 | 
			
		||||
    stopById(id: SoundIds) {
 | 
			
		||||
        const se = this.get(id);
 | 
			
		||||
        se.stopAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 停止所有音效的播放
 | 
			
		||||
     */
 | 
			
		||||
    stopAll() {
 | 
			
		||||
        Object.values(this.list).forEach(v => v.stopAll());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取一个音效实例
 | 
			
		||||
     * @param sound 音效名称
 | 
			
		||||
     */
 | 
			
		||||
    get(sound: SoundIds) {
 | 
			
		||||
        return this.list[`sounds.${sound}`];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,15 @@
 | 
			
		||||
import resource from '../../data/resource.json';
 | 
			
		||||
import { NonZipResource, Resource, getTypeByResource } from './resource';
 | 
			
		||||
import { Resource, getTypeByResource } from './resource';
 | 
			
		||||
 | 
			
		||||
const info = resource as ResourceInfo[];
 | 
			
		||||
 | 
			
		||||
export interface ResourceInfo {
 | 
			
		||||
    includes: string[];
 | 
			
		||||
    zip: boolean;
 | 
			
		||||
    zippedName?: string;
 | 
			
		||||
}
 | 
			
		||||
const info = resource;
 | 
			
		||||
 | 
			
		||||
export function readyAllResource() {
 | 
			
		||||
    info.forEach(v => {
 | 
			
		||||
        if (v.zip) {
 | 
			
		||||
            const id = `zip.${v.zippedName}`;
 | 
			
		||||
            ancTe.zipResource.push([[id, new Resource(id, 'zip')]]);
 | 
			
		||||
        } else {
 | 
			
		||||
            const res: [string, Resource<NonZipResource>][] = v.includes.map(
 | 
			
		||||
                v => {
 | 
			
		||||
        const type = getTypeByResource(v);
 | 
			
		||||
                    return [v, new Resource(v, type as NonZipResource)];
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
            ancTe.resource.push(res);
 | 
			
		||||
        if (type === 'zip') {
 | 
			
		||||
            ancTe.zipResource.set(v, new Resource(v, 'zip'));
 | 
			
		||||
        } else {
 | 
			
		||||
            ancTe.resource.set(v, new Resource(v, type));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,3 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "includes": [],
 | 
			
		||||
        "zip": false
 | 
			
		||||
    }
 | 
			
		||||
    ""
 | 
			
		||||
]
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user