8000 migrate audio bug fix on 2D && support audio clone and AudioManager by PPpro · Pull Request #7192 · cocos/cocos-engine · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

migrate audio bug fix on 2D && support audio clone and AudioManager #7192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Oct 19, 2020
Merged
25 changes: 25 additions & 0 deletions cocos/audio/assets/audio-manager.ts
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { legacyCC } from "../../core/global-exports";

export abstract class AudioManager<AudioType> {
protected _playingAudios: Array<AudioType>;
public static readonly maxAudioChannel: number = 24;

constructor () {
this._playingAudios = [];
}

public addPlaying (audio: AudioType) {
this._playingAudios.push(audio);
}

public removePlaying (audio: AudioType) {
let index = this._playingAudios.indexOf(audio);
if (index > -1) {
this._playingAudios.splice(index, 1);
}
}

public abstract discardOnePlayingIfNeeded ();
}

legacyCC.internal.AudioManager = AudioManager;
14 changes: 7 additions & 7 deletions cocos/audio/assets/clip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class AudioClip extends Asset {
@type(AudioType)
protected _loadMode = AudioType.UNKNOWN_AUDIO;

protected _audio: any = null;
protected _nativeAudio: any = null;
protected _player: AudioPlayer | null = null;

constructor () {
Expand All @@ -81,11 +81,11 @@ export class AudioClip extends Asset {
return super.destroy();
}

set _nativeAsset (clip: any) {
this._audio = clip;
if (clip) {
const ctor = this._getPlayer(clip);
this._player = new ctor({ clip, duration: this._duration, eventTarget: this });
set _nativeAsset (nativeAudio: any) {
this._nativeAudio = nativeAudio;
if (nativeAudio) {
const ctor = this._getPlayer(nativeAudio);
this._player = new ctor({ nativeAudio, duration: this._duration, audioClip: this });
this.loaded = true;
this.emit('load');
} else {
Expand All @@ -97,7 +97,7 @@ export class AudioClip extends Asset {
}

get _nativeAsset () {
return this._audio;
return this._nativeAudio;
}

get loadMode () {
Expand Down
120 changes: 74 additions & 46 deletions cocos/audio/assets/player-dom.ts
8305
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,36 @@
import { clamp } from '../../core/math/utils';
import { AudioPlayer, IAudioInfo, PlayingState } from './player';
import { legacyCC } from '../../core/global-exports';
import { createDomAudio } from '../audio-downloader';
import { AudioManager } from './audio-manager';

type ManagedAudio = AudioPlayerDOM | HTMLAudioElement;
class AudioManagerDom extends AudioManager<ManagedAudio> {
public discardOnePlayingIfNeeded() {
if (this._playingAudios.length < AudioManager.maxAudioChannel) {
return;
}

// a played audio has a higher priority than a played shot
let audioToDiscard: ManagedAudio | undefined;
let oldestOneShotIndex = this._playingAudios.findIndex(audio => audio instanceof HTMLAudioElement);
if (oldestOneShotIndex > -1) {
audioToDiscard = this._playingAudios[oldestOneShotIndex] as HTMLAudioElement;
this._playingAudios.splice(oldestOneShotIndex, 1);
audioToDiscard.pause();
audioToDiscard.src = '';
}
else {
audioToDiscard = this._playingAudios.shift();
(<AudioPlayerDOM>audioToDiscard)?.stop();
}
}
}
export class AudioPlayerDOM extends AudioPlayer {
protected static _manager: AudioManagerDom = new AudioManagerDom();
protected _volume = 1;
protected _loop = false;
protected _oneShotOngoing = false;
protected _audio: HTMLAudioElement;
protected _nativeAudio: HTMLAudioElement;
protected _cbRegistered = false;

private _remove_cb: () => void;
Expand All @@ -46,7 +70,7 @@ export class AudioPlayerDOM extends AudioPlayer {

constructor (info: IAudioInfo) {
super(info);
this._audio = info.clip;
this._nativeAudio = info.nativeAudio;

this._remove_cb = () => {
if (!this._cbRegistered) { return; }
Expand All @@ -57,18 +81,19 @@ export class AudioPlayerDOM extends AudioPlayer {

this._post_play = () => {
this._state = PlayingState.PLAYING;
this._eventTarget.emit('started');
this._clip.emit('started');
this._remove_cb(); // should remove callbacks after any success play
AudioPlayerDOM._manager.addPlaying(this);
};

this._post_gesture = () => {
if (this._interrupted) { this._post_play(); this._interrupted = false; }
else { this._audio!.pause(); this._audio!.currentTime = 0; }
else { this._nativeAudio!.pause(); this._nativeAudio!.currentTime = 0; }
};

this._on_gesture = () => {
if (!this._audio) { return; }
const promise = this._audio.play();
if (!this._nativeAudio) { return; }
const promise = this._nativeAudio.play();
if (!promise) { // Chrome50/Firefox53 below
// delay eval here to yield uniform behavior with other platforms
this._state = PlayingState.PLAYING;
Expand All @@ -79,14 +104,14 @@ export class AudioPlayerDOM extends AudioPlayer {
this._remove_cb();
};

this._audio.volume = this._volume;
this._audio.loop = this._loop;
this._nativeAudio.volume = this._volume;
this._nativeAudio.loop = this._loop;
// callback on audio ended
this._audio.addEventListener('ended', () => {
if (this._oneShotOngoing) { return; }
this._nativeAudio.addEventListener('ended', () => {
this._state = PlayingState.STOPPED;
this._audio!.currentTime = 0;
this._eventTarget.emit('ended');
this._nativeAudio!.currentTime = 0;
this._clip.emit('ended');
AudioPlayerDOM._manager.removePlaying(this);
});
/* play & stop immediately after receiving a gesture so that
we can freely invoke play() outside event listeners later */
Expand All @@ -96,9 +121,18 @@ export class AudioPlayerDOM extends AudioPlayer {
}

public play () {
if (!this._audio || this._state === PlayingState.PLAYING) { return; }
if (!this._nativeAudio) { return; }
if (this._blocking) { this._interrupted = true; return; }
const promise = this._audio.play();
if (this._state === PlayingState.PLAYING) {
/* sometimes there is no way to update the playing state
especially when player unplug earphones and the audio automatically stops
so we need to force updating the playing state by pausing audio */
this.pause();
// restart if already playing
this.setCurrentTime(0);
}
AudioPlayerDOM._manager.discardOnePlayingIfNeeded();
const promise = this._nativeAudio.play();
if (!promise) {
// delay eval here to yield uniform behavior with other platforms
this._state = PlayingState.PLAYING;
Expand All @@ -109,75 +143,69 @@ export class AudioPlayerDOM extends AudioPlayer {
}

public pause () {
if (!this._audio) { return; }
if (!this._nativeAudio) { return; }
this._interrupted = false;
if (this._state !== PlayingState.PLAYING) { return; }
this._audio.pause();
this._nativeAudio.pause();
this._state = PlayingState.STOPPED;
this._oneShotOngoing = false;
AudioPlayerDOM._manager.removePlaying(this);
}

public stop () {
if (!this._audio) { return; }
this._audio.currentTime = 0; this._interrupted = false;
if (!this._nativeAudio) { return; }
this._nativeAudio.currentTime = 0; this._interrupted = false;
if (this._state !== PlayingState.PLAYING) { return; }
this._audio.pause();
this._nativeAudio.pause();
this._state = PlayingState.STOPPED;
this._oneShotOngoing = false;
AudioPlayerDOM._manager.removePlaying(this);
}

public playOneShot (volume = 1) {
/* HTMLMediaElement doesn't support multiple playback at the
same time so here we fall back to re-start style approach */
const clip = this._audio;
if (!clip) { return; }
clip.currentTime = 0;
clip.volume = volume;
if (this._oneShotOngoing) { return; }
clip.loop = false;
this._oneShotOngoing = true;
clip.play().then(() => {
clip.addEventListener('ended', () => {
clip.currentTime = 0;
clip.volume = this._volume;
clip.loop = this._loop;
this._oneShotOngoing = false;
}, { once: true });
}).catch(() => { this._oneShotOngoing = false; });
createDomAudio(this._nativeAudio.src).then(dom => {
AudioPlayerDOM._manager.discardOnePlayingIfNeeded();
dom.volume = volume;
dom.play();
AudioPlayerDOM._manager.addPlaying(dom);
dom.addEventListener('ended', () => {
AudioPlayerDOM._manager.removePlaying(dom);
});
}, errMsg => {
console.error(errMsg);
});
}

public setCurrentTime (val: number) {
if (!this._audio) { return; }
this._audio.currentTime = clamp(val, 0, this._duration);
if (!this._nativeAudio) { return; }
this._nativeAudio.currentTime = clamp(val, 0, this._duration);
}

public getCurrentTime () {
return this._audio ? this._audio.currentTime : 0;
return this._nativeAudio ? this._nativeAudio.currentTime : 0;
}

public setVolume (val: number, immediate: boolean) {
this._volume = val;
/* note this won't work for ios devices, for there
is just no way to set HTMLMediaElement's volume */
if (this._audio) { this._audio.volume = val; }
if (this._nativeAudio) { this._nativeAudio.volume = val; }
}

public getVolume () {
if (this._audio) { return this._audio.volume; }
if (this._nativeAudio) { return this._nativeAudio.volume; }
return this._volume;
}

public setLoop (val: boolean) {
this._loop = val;
if (this._audio) { this._audio.loop = val; }
if (this._nativeAudio) { this._nativeAudio.loop = val; }
}

public getLoop () {
return this._loop;
}

public destroy () {
if (this._audio) { this._audio.src = ''; }
if (this._nativeAudio) { this._nativeAudio.src = ''; }
super.destroy();
}
}
Loading
0