@@ -6,9 +6,11 @@ import { getBooleanArgByKey, getNumberArgByKey, getStringArgByKey } from '@/Core
66import { IStageState } from '@/store/stageInterface' ;
77import {
88 audioContextWrapper ,
9+ ensureAudioContextReady ,
910 getAudioLevel ,
1011 performBlinkAnimation ,
1112 performMouthAnimation ,
13+ resetMaxAudioLevel ,
1214 updateThresholds ,
1315} from '@/Core/gameScripts/vocal/vocalAnimation' ;
1416import { match } from '../../util/match' ;
@@ -64,7 +66,7 @@ export const playVocal = (sentence: ISentence) => {
6466 return {
6567 arrangePerformPromise : new Promise ( ( resolve ) => {
6668 // 播放语音
67- setTimeout ( ( ) => {
69+ setTimeout ( async ( ) => {
6870 let VocalControl : any = document . getElementById ( 'currentVocal' ) ;
6971 // 设置语音音量
7072 webgalStore . dispatch ( setStage ( { key : 'vocalVolume' , value : volume } ) ) ;
@@ -102,64 +104,71 @@ export const playVocal = (sentence: ISentence) => {
102104 stopTimeout : undefined , // 暂时不用,后面会交给自动清除
103105 } ;
104106 WebGAL . gameplay . performController . arrangeNewPerform ( perform , sentence , false ) ;
107+ const finishPerform = ( ) => {
108+ for ( const e of WebGAL . gameplay . performController . performList ) {
109+ if ( e . performName === performInitName ) {
110+ isOver = true ;
111+ e . stopFunction ( ) ;
112+ WebGAL . gameplay . performController . unmountPerform ( e . performName ) ;
113+ }
114+ }
115+ } ;
116+
105117 key = key ? key : `fig-${ pos } ` ;
106118 const animationItem = figureAssociatedAnimation . find ( ( tid ) => tid . targetId === key ) ;
107119 if ( animationItem ) {
108- let maxAudioLevel = 0 ;
120+ resetMaxAudioLevel ( ) ;
109121
110122 const foundFigure = freeFigure . find ( ( figure ) => figure . key === key ) ;
111123
112124 if ( foundFigure ) {
113125 pos = foundFigure . basePosition ;
114126 }
115127
116- if ( ! audioContextWrapper . audioContext ) {
117- let audioContext : AudioContext | null ;
118- audioContext = new AudioContext ( ) ;
119- audioContextWrapper . analyser = audioContext . createAnalyser ( ) ;
120- audioContextWrapper . analyser . fftSize = 256 ;
121- audioContextWrapper . dataArray = new Uint8Array ( audioContextWrapper . analyser . frequencyBinCount ) ;
122- }
123-
124- if ( ! audioContextWrapper . analyser ) {
125- audioContextWrapper . analyser = audioContextWrapper . audioContext . createAnalyser ( ) ;
126- audioContextWrapper . analyser . fftSize = 256 ;
127- }
128-
129- bufferLength = audioContextWrapper . analyser . frequencyBinCount ;
130- audioContextWrapper . dataArray = new Uint8Array ( bufferLength ) ;
131- let vocalControl = document . getElementById ( 'currentVocal' ) as HTMLMediaElement ;
132-
133- if ( ! audioContextWrapper . source || audioContextWrapper . source . mediaElement !== vocalControl ) {
134- if ( audioContextWrapper . source ) {
135- audioContextWrapper . source . disconnect ( ) ;
128+ const isAudioContextReady = await ensureAudioContextReady ( ) ;
129+ if ( isAudioContextReady && audioContextWrapper . audioContext ) {
130+ if ( ! audioContextWrapper . analyser ) {
131+ audioContextWrapper . analyser = audioContextWrapper . audioContext . createAnalyser ( ) ;
132+ audioContextWrapper . analyser . fftSize = 256 ;
136133 }
137- audioContextWrapper . source = audioContextWrapper . audioContext . createMediaElementSource ( vocalControl ) ;
138- audioContextWrapper . source . connect ( audioContextWrapper . analyser ! ) ;
139- }
140134
141- audioContextWrapper . analyser . connect ( audioContextWrapper . audioContext . destination ) ;
135+ bufferLength = audioContextWrapper . analyser . frequencyBinCount ;
136+ audioContextWrapper . dataArray = new Uint8Array ( bufferLength ) ;
137+ let vocalControl = document . getElementById ( 'currentVocal' ) as HTMLMediaElement ;
142138
143- // Lip-snc Animation
144- audioContextWrapper . audioLevelInterval = setInterval ( ( ) => {
145- const audioLevel = getAudioLevel (
146- audioContextWrapper . analyser ! ,
147- audioContextWrapper . dataArray ! ,
148- bufferLength ,
149- ) ;
150- const { OPEN_THRESHOLD , HALF_OPEN_THRESHOLD } = updateThresholds ( audioLevel ) ;
139+ if ( ! audioContextWrapper . source || audioContextWrapper . source . mediaElement !== vocalControl ) {
140+ if ( audioContextWrapper . source ) {
141+ audioContextWrapper . source . disconnect ( ) ;
142+ }
143+ audioContextWrapper . source = audioContextWrapper . audioContext . createMediaElementSource ( vocalControl ) ;
144+ audioContextWrapper . source . connect ( audioContextWrapper . analyser ) ;
145+ }
151146
152- performMouthAnimation ( {
153- audioLevel,
154- OPEN_THRESHOLD ,
155- HALF_OPEN_THRESHOLD ,
156- currentMouthValue,
157- lerpSpeed,
158- key,
159- animationItem,
160- pos,
161- } ) ;
162- } , 50 ) ;
147+ audioContextWrapper . analyser . connect ( audioContextWrapper . audioContext . destination ) ;
148+
149+ // Lip-sync Animation
150+ audioContextWrapper . audioLevelInterval = setInterval ( ( ) => {
151+ const audioLevel = getAudioLevel (
152+ audioContextWrapper . analyser ! ,
153+ audioContextWrapper . dataArray ! ,
154+ bufferLength ,
155+ ) ;
156+ const { OPEN_THRESHOLD , HALF_OPEN_THRESHOLD } = updateThresholds ( audioLevel ) ;
157+
158+ performMouthAnimation ( {
159+ audioLevel,
160+ OPEN_THRESHOLD ,
161+ HALF_OPEN_THRESHOLD ,
162+ currentMouthValue,
163+ lerpSpeed,
164+ key,
165+ animationItem,
166+ pos,
167+ } ) ;
168+ } , 50 ) ;
169+ } else {
170+ logger . warn ( 'AudioContext is not ready, skip lip-sync analyzer for this vocal.' ) ;
171+ }
163172
164173 // blinkAnimation
165174 let animationEndTime : number ;
@@ -174,17 +183,16 @@ export const playVocal = (sentence: ISentence) => {
174183 } , 10000 ) ;
175184 }
176185
177- VocalControl ?. play ( ) ;
186+ const playPromise = VocalControl ?. play ( ) ;
178187
179- VocalControl . onended = ( ) => {
180- for ( const e of WebGAL . gameplay . performController . performList ) {
181- if ( e . performName === performInitName ) {
182- isOver = true ;
183- e . stopFunction ( ) ;
184- WebGAL . gameplay . performController . unmountPerform ( e . performName ) ;
185- }
186- }
187- } ;
188+ if ( playPromise ?. catch ) {
189+ playPromise . catch ( ( error : unknown ) => {
190+ logger . warn ( 'Vocal play was blocked by browser autoplay policy or audio activation state.' , error ) ;
191+ finishPerform ( ) ;
192+ } ) ;
193+ }
194+
195+ VocalControl . onended = finishPerform ;
188196 }
189197 } , 1 ) ;
190198 } ) ,
0 commit comments