Skip to content

Commit

Permalink
Merge pull request #2449 from GuoLei1990/feat/audio
Browse files Browse the repository at this point in the history
feat: support background audio
  • Loading branch information
GuoLei1990 authored Dec 7, 2024
2 parents 34e0879 + 3de87e1 commit 41a673d
Show file tree
Hide file tree
Showing 21 changed files with 564 additions and 52 deletions.
4 changes: 3 additions & 1 deletion packages/core/src/asset/AssetType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ export enum AssetType {
HDR = "HDR",
/** Font. */
Font = "Font",
/** Source Font, include ttf otf and woff. */
/** Source Font, include ttf, otf and woff. */
SourceFont = "SourceFont",
/** AudioClip, include ogg, wav and mp3. */
Audio = "Audio",
/** Project asset. */
Project = "project"
}
1 change: 0 additions & 1 deletion packages/core/src/asset/Loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Engine, EngineConfiguration } from "../Engine";
import { AssetPromise } from "./AssetPromise";
import { LoadItem } from "./LoadItem";
import { request, RequestConfig } from "./request";
import { ResourceManager } from "./ResourceManager";
/**
* Loader abstract class.
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/asset/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const defaultRetryCount = 1;
const defaultTimeout = Infinity;
const defaultInterval = 500;

/**
* Configuration options for `request`.
* @remarks
* This type extends the standard `RequestInit` options with additional
* properties for handling retries, timeouts, and custom response types.
*/
export type RequestConfig = {
type?: XMLHttpRequestResponseType | "image";
retryCount?: number;
Expand All @@ -24,9 +30,10 @@ export type RequestConfig = {
} & RequestInit;

/**
* Web request.
* @param url - The link
* @param config - Load configuration
* Sends a request to the specified URL and returns a promise for the response.
* @param url - The URL to send the request to
* @param config - Configuration options for the request
* @returns A promise that resolves with the response of type `T`
*/
export function request<T>(url: string, config: RequestConfig = {}): AssetPromise<T> {
return new AssetPromise((resolve, reject, setTaskCompleteProgress, setTaskDetailProgress) => {
Expand Down
58 changes: 58 additions & 0 deletions packages/core/src/audio/AudioClip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Engine } from "../Engine";
import { ReferResource } from "../asset/ReferResource";

/**
* Audio Clip.
*/
export class AudioClip extends ReferResource {
private _audioBuffer: AudioBuffer | null = null;

/** Name of clip. */
name: string;

/**
* Number of discrete audio channels.
*/
get channels(): number {
return this._audioBuffer.numberOfChannels;
}

/**
* Sample rate, in samples per second.
*/
get sampleRate(): number {
return this._audioBuffer.sampleRate;
}

/**
* Duration, in seconds.
*/
get duration(): number {
return this._audioBuffer.duration;
}

constructor(engine: Engine, name: string = "") {
super(engine);
this.name = name;
}

/**
* @internal
*/
_getAudioSource(): AudioBuffer {
return this._audioBuffer;
}

/**
* @internal
*/
_setAudioSource(value: AudioBuffer): void {
this._audioBuffer = value;
}

protected override _onDestroy(): void {
super._onDestroy();
this._audioBuffer = null;
this.name = null;
}
}
52 changes: 52 additions & 0 deletions packages/core/src/audio/AudioManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @internal
* Audio Manager.
*/
export class AudioManager {
private static _context: AudioContext;
private static _gainNode: GainNode;
private static _isResuming = false;

static getContext(): AudioContext {
let context = AudioManager._context;
if (!context) {
AudioManager._context = context = new window.AudioContext();

// Safari can't resume audio context without element interaction
document.addEventListener("pointerdown", AudioManager._tryResume, true);
document.addEventListener("touchend", AudioManager._tryResume, true);
document.addEventListener("touchstart", AudioManager._tryResume, true);
}
return context;
}

static getGainNode(): GainNode {
let gainNode = AudioManager._gainNode;
if (!AudioManager._gainNode) {
AudioManager._gainNode = gainNode = AudioManager.getContext().createGain();
gainNode.connect(AudioManager.getContext().destination);
}
return gainNode;
}

static isAudioContextRunning(): boolean {
if (AudioManager.getContext().state !== "running") {
console.warn("The AudioContext is not running and requires user interaction, such as a click or touch.");
return false;
}
return true;
}

private static _tryResume(): void {
if (AudioManager._context.state !== "running") {
if (AudioManager._isResuming) {
return;
}

AudioManager._isResuming = true;
AudioManager._context.resume().then(() => {
AudioManager._isResuming = false;
});
}
}
}
Loading

0 comments on commit 41a673d

Please sign in to comment.