未验证 提交 1a9b2322 编写于 作者: B bofeng-song 提交者: GitHub

fix audio can not reset to position 0 after call stop operation on web platform (#16122)

* fix audio can not reset to position 0 after call stop operation on web platform
上级 aa3b35e9
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
import { minigame } from 'pal/minigame'; import { minigame } from 'pal/minigame';
import { systemInfo } from 'pal/system-info'; import { systemInfo } from 'pal/system-info';
import { clamp01 } from '../../../cocos/core'; import { clamp01 } from '../../../cocos/core';
import * as debug from '../../../cocos/core/platform/debug';
import { EventTarget } from '../../../cocos/core/event'; import { EventTarget } from '../../../cocos/core/event';
import { audioBufferManager } from '../audio-buffer-manager'; import { audioBufferManager } from '../audio-buffer-manager';
import AudioTimer from '../audio-timer'; import AudioTimer from '../audio-timer';
...@@ -133,7 +134,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -133,7 +134,7 @@ export class AudioPlayerWeb implements OperationQueueable {
this._state = AudioState.INTERRUPTED; this._state = AudioState.INTERRUPTED;
this._readyToHandleOnShow = true; this._readyToHandleOnShow = true;
this._eventTarget.emit(AudioEvent.INTERRUPTION_BEGIN); this._eventTarget.emit(AudioEvent.INTERRUPTION_BEGIN);
}).catch((e) => {}); }).catch((e) => { debug.warn('_onInterruptedBegin error', e); });
} }
} }
private _onInterruptedEnd (): void { private _onInterruptedEnd (): void {
...@@ -145,7 +146,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -145,7 +146,7 @@ export class AudioPlayerWeb implements OperationQueueable {
if (this._state === AudioState.INTERRUPTED) { if (this._state === AudioState.INTERRUPTED) {
this.play().then(() => { this.play().then(() => {
this._eventTarget.emit(AudioEvent.INTERRUPTION_END); this._eventTarget.emit(AudioEvent.INTERRUPTION_END);
}).catch((e) => {}); }).catch((e) => { debug.warn('_onInterruptedEnd error', e); });
} }
this._readyToHandleOnShow = false; this._readyToHandleOnShow = false;
} }
...@@ -153,7 +154,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -153,7 +154,7 @@ export class AudioPlayerWeb implements OperationQueueable {
return new Promise((resolve) => { return new Promise((resolve) => {
AudioPlayerWeb.loadNative(url).then((audioBuffer) => { AudioPlayerWeb.loadNative(url).then((audioBuffer) => {
resolve(new AudioPlayerWeb(audioBuffer, url)); resolve(new AudioPlayerWeb(audioBuffer, url));
}).catch((e) => {}); }).catch((e) => { debug.warn('load error', url, e); });
}); });
} }
static loadNative (url: string): Promise<AudioBuffer> { static loadNative (url: string): Promise<AudioBuffer> {
...@@ -175,7 +176,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -175,7 +176,7 @@ export class AudioPlayerWeb implements OperationQueueable {
audioContext!.decodeAudioData(arrayBuffer).then((decodedAudioBuffer) => { audioContext!.decodeAudioData(arrayBuffer).then((decodedAudioBuffer) => {
audioBufferManager.addCache(url, decodedAudioBuffer); audioBufferManager.addCache(url, decodedAudioBuffer);
resolve(decodedAudioBuffer); resolve(decodedAudioBuffer);
}).catch((e) => {}); }).catch((e) => { debug.warn('loadNative error', url, e); });
}); });
}); });
} }
...@@ -185,6 +186,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -185,6 +186,7 @@ export class AudioPlayerWeb implements OperationQueueable {
AudioPlayerWeb.loadNative(url).then((audioBuffer) => { AudioPlayerWeb.loadNative(url).then((audioBuffer) => {
// HACK: AudioPlayer should be a friend class in OneShotAudio // HACK: AudioPlayer should be a friend class in OneShotAudio
const oneShotAudio = new (OneShotAudioWeb as any)(audioBuffer, volume, url); const oneShotAudio = new (OneShotAudioWeb as any)(audioBuffer, volume, url);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resolve(oneShotAudio); resolve(oneShotAudio);
}).catch(reject); }).catch(reject);
}); });
...@@ -238,7 +240,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -238,7 +240,7 @@ export class AudioPlayerWeb implements OperationQueueable {
if (this._state === AudioState.PLAYING) { if (this._state === AudioState.PLAYING) {
// one AudioBufferSourceNode can't start twice // one AudioBufferSourceNode can't start twice
// need to create a new one to start from the offset // need to create a new one to start from the offset
this._doPlay().then(resolve).catch((e) => {}); this._doPlay().then(resolve).catch((e) => { debug.warn('seek error', e); });
} else { } else {
resolve(); resolve();
} }
...@@ -302,6 +304,8 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -302,6 +304,8 @@ export class AudioPlayerWeb implements OperationQueueable {
@enqueueOperation @enqueueOperation
stop (): Promise<void> { stop (): Promise<void> {
if (!this._sourceNode) { if (!this._sourceNode) {
this._audioTimer.stop();
this._state = AudioState.STOPPED;
return Promise.resolve(); return Promise.resolve();
} }
this._audioTimer.stop(); this._audioTimer.stop();
......
...@@ -26,6 +26,7 @@ import { EDITOR_NOT_IN_PREVIEW } from 'internal:constants'; ...@@ -26,6 +26,7 @@ import { EDITOR_NOT_IN_PREVIEW } from 'internal:constants';
import { AudioPCMDataView, AudioEvent, AudioState, AudioType } from '../type'; import { AudioPCMDataView, AudioEvent, AudioState, AudioType } from '../type';
import { EventTarget } from '../../../cocos/core/event'; import { EventTarget } from '../../../cocos/core/event';
import { clamp01 } from '../../../cocos/core'; import { clamp01 } from '../../../cocos/core';
import * as debug from '../../../cocos/core/platform/debug';
import { enqueueOperation, OperationInfo, OperationQueueable } from '../operation-queue'; import { enqueueOperation, OperationInfo, OperationQueueable } from '../operation-queue';
import AudioTimer from '../audio-timer'; import AudioTimer from '../audio-timer';
import { audioBufferManager } from '../audio-buffer-manager'; import { audioBufferManager } from '../audio-buffer-manager';
...@@ -76,9 +77,10 @@ export class AudioContextAgent { ...@@ -76,9 +77,10 @@ export class AudioContextAgent {
resolve(audioBuffer); resolve(audioBuffer);
}, (err) => { }, (err) => {
// TODO: need to reject the error. // TODO: need to reject the error.
// eslint-disable-next-line no-console
console.error('failed to load Web Audio', err); console.error('failed to load Web Audio', err);
}); });
promise?.catch((e) => {}); // Safari doesn't support the promise based decodeAudioData promise?.catch((e) => { debug.warn('decodeAudioData error', e); }); // Safari doesn't support the promise based decodeAudioData
}); });
} }
...@@ -93,7 +95,7 @@ export class AudioContextAgent { ...@@ -93,7 +95,7 @@ export class AudioContextAgent {
resolve(); resolve();
return; return;
} }
context.resume().catch((e) => {}); context.resume().catch((e) => { debug.warn('runContext error', e); });
if (context.state === 'running') { if (context.state === 'running') {
resolve(); resolve();
return; return;
...@@ -106,7 +108,7 @@ export class AudioContextAgent { ...@@ -106,7 +108,7 @@ export class AudioContextAgent {
canvas?.removeEventListener('touchend', onGesture, { capture: true }); canvas?.removeEventListener('touchend', onGesture, { capture: true });
canvas?.removeEventListener('mouseup', onGesture, { capture: true }); canvas?.removeEventListener('mouseup', onGesture, { capture: true });
resolve(); resolve();
}).catch((e) => {}); }).catch((e) => { debug.warn('onGesture resume error', e); });
}; };
canvas?.addEventListener('touchend', onGesture, { capture: true }); canvas?.addEventListener('touchend', onGesture, { capture: true });
canvas?.addEventListener('mouseup', onGesture, { capture: true }); canvas?.addEventListener('mouseup', onGesture, { capture: true });
...@@ -199,7 +201,7 @@ export class OneShotAudioWeb { ...@@ -199,7 +201,7 @@ export class OneShotAudioWeb {
audioBufferManager.tryReleasingCache(this._url); audioBufferManager.tryReleasingCache(this._url);
this.onEnd?.(); this.onEnd?.();
}, this._duration * 1000); }, this._duration * 1000);
}).catch((e) => {}); }).catch((e) => { debug.warn('play error', e); });
} }
public stop (): void { public stop (): void {
...@@ -257,7 +259,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -257,7 +259,7 @@ export class AudioPlayerWeb implements OperationQueueable {
return new Promise((resolve) => { return new Promise((resolve) => {
AudioPlayerWeb.loadNative(url).then((audioBuffer) => { AudioPlayerWeb.loadNative(url).then((audioBuffer) => {
resolve(new AudioPlayerWeb(audioBuffer, url)); resolve(new AudioPlayerWeb(audioBuffer, url));
}).catch((e) => {}); }).catch((e) => { debug.warn('load error', url, e); });
}); });
} }
static loadNative (url: string): Promise<AudioBuffer> { static loadNative (url: string): Promise<AudioBuffer> {
...@@ -275,10 +277,11 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -275,10 +277,11 @@ export class AudioPlayerWeb implements OperationQueueable {
xhr.onload = (): void => { xhr.onload = (): void => {
if (xhr.status === 200 || xhr.status === 0) { if (xhr.status === 200 || xhr.status === 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
audioContextAgent!.decodeAudioData(xhr.response).then((decodedAudioBuffer) => { audioContextAgent!.decodeAudioData(xhr.response).then((decodedAudioBuffer) => {
audioBufferManager.addCache(url, decodedAudioBuffer); audioBufferManager.addCache(url, decodedAudioBuffer);
resolve(decodedAudioBuffer); resolve(decodedAudioBuffer);
}).catch((e) => {}); }).catch((e) => { debug.warn('loadNative error', url, e); });
} else { } else {
reject(new Error(`${errInfo}${xhr.status}(no response)`)); reject(new Error(`${errInfo}${xhr.status}(no response)`));
} }
...@@ -295,6 +298,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -295,6 +298,7 @@ export class AudioPlayerWeb implements OperationQueueable {
AudioPlayerWeb.loadNative(url).then((audioBuffer) => { AudioPlayerWeb.loadNative(url).then((audioBuffer) => {
// HACK: AudioPlayer should be a friend class in OneShotAudio // HACK: AudioPlayer should be a friend class in OneShotAudio
const oneShotAudio = new (OneShotAudioWeb as any)(audioBuffer, volume, url); const oneShotAudio = new (OneShotAudioWeb as any)(audioBuffer, volume, url);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resolve(oneShotAudio); resolve(oneShotAudio);
}).catch(reject); }).catch(reject);
}); });
...@@ -313,14 +317,14 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -313,14 +317,14 @@ export class AudioPlayerWeb implements OperationQueueable {
this.pause().then(() => { this.pause().then(() => {
this._state = AudioState.INTERRUPTED; this._state = AudioState.INTERRUPTED;
this._eventTarget.emit(AudioEvent.INTERRUPTION_BEGIN); this._eventTarget.emit(AudioEvent.INTERRUPTION_BEGIN);
}).catch((e) => {}); }).catch((e) => { debug.warn('_onInterruptedBegin error', e); });
} }
} }
private _onInterruptedEnd (): void { private _onInterruptedEnd (): void {
if (this._state === AudioState.INTERRUPTED) { if (this._state === AudioState.INTERRUPTED) {
this.play().then(() => { this.play().then(() => {
this._eventTarget.emit(AudioEvent.INTERRUPTION_END); this._eventTarget.emit(AudioEvent.INTERRUPTION_END);
}).catch((e) => {}); }).catch((e) => { debug.warn('_onInterruptedEnd error', e); });
} }
} }
...@@ -371,7 +375,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -371,7 +375,7 @@ export class AudioPlayerWeb implements OperationQueueable {
if (this._state === AudioState.PLAYING) { if (this._state === AudioState.PLAYING) {
// one AudioBufferSourceNode can't start twice // one AudioBufferSourceNode can't start twice
// need to create a new one to start from the offset // need to create a new one to start from the offset
this._doPlay().then(resolve).catch((e) => {}); this._doPlay().then(resolve).catch((e) => { debug.warn('seek error', e); });
} else { } else {
resolve(); resolve();
} }
...@@ -405,7 +409,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -405,7 +409,7 @@ export class AudioPlayerWeb implements OperationQueueable {
// - system automatically resume audio context when enter foreground from background. // - system automatically resume audio context when enter foreground from background.
audioContextAgent!.onceRunning(this._runningCallback); audioContextAgent!.onceRunning(this._runningCallback);
// Ensure resume context. // Ensure resume context.
audioContextAgent!.runContext().catch((e) => {}); audioContextAgent!.runContext().catch((e) => { debug.warn('doPlay error', e); });
} }
}); });
} }
...@@ -415,6 +419,7 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -415,6 +419,7 @@ export class AudioPlayerWeb implements OperationQueueable {
this._stopSourceNode(); this._stopSourceNode();
this._sourceNode = audioContextAgent!.createBufferSource(this._audioBuffer, this.loop); this._sourceNode = audioContextAgent!.createBufferSource(this._audioBuffer, this.loop);
this._sourceNode.connect(this._gainNode); this._sourceNode.connect(this._gainNode);
this._sourceNode.loop = this._loop;
this._sourceNode.start(0, this._audioTimer.currentTime); this._sourceNode.start(0, this._audioTimer.currentTime);
this._state = AudioState.PLAYING; this._state = AudioState.PLAYING;
this._audioTimer.start(); this._audioTimer.start();
...@@ -465,6 +470,8 @@ export class AudioPlayerWeb implements OperationQueueable { ...@@ -465,6 +470,8 @@ export class AudioPlayerWeb implements OperationQueueable {
stop (): Promise<void> { stop (): Promise<void> {
this.offRunning(); this.offRunning();
if (!this._sourceNode) { if (!this._sourceNode) {
this._audioTimer.stop();
this._state = AudioState.STOPPED;
return Promise.resolve(); return Promise.resolve();
} }
this._audioTimer.stop(); this._audioTimer.stop();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册