.NET 9 MAUI HybridWebView中Azure语音化身无音频问题求助
.NET 9 MAUI Android应用Azure Speech to Avatar无音频输出问题
我正在开发面向Android平台的.NET 9 MAUI移动应用,通过HybridWebView加载Azure Speech to Avatar视频,当前能正常显示化身画面及唇部动作,但无音频输出。
1. XAML中HybridWebView配置
<StackLayout Grid.Row="0" Orientation="Horizontal" VerticalOptions="Center" Margin="0,10,0,10"> <Grid RowDefinitions="Auto,*" ColumnDefinitions="*" MaximumWidthRequest="400" MaximumHeightRequest="1000"> <HybridWebView x:Name="hybridWebView" RawMessageReceived="OnHybridWebViewRawMessageReceived" Grid.Row="1" /> </Grid> </StackLayout>
2. .NET调用JS生成化身的代码
var result = await hybridWebView.InvokeJavaScriptAsync("speak", HybridJsContext.Default.DictionaryStringString, [com.Message, Controllers.CurrentLanguage], [HybridJsContext.Default.String, HybridJsContext.Default.String]);
3. HTML页面结构
<div class="avatar-container"> <div id="videoContainer"> <div id="overlayArea" style="position: absolute; top: 0; left: 0;" hidden="hidden"> <p id="overlayText" style="font-size: large;"></p> <img id="overlayImage" src="your-image-source.png" alt="Overlay Image"> </div> <div id="remoteVideo"></div> <canvas id="canvas" width="1920" height="1080" style="background-color: transparent; background-image: url('AvatarBackground.jpg'); background-size: cover; background-position: center;" hidden="hidden"></canvas> <canvas id="tmpCanvas" width="1920" height="1080" hidden="hidden"></canvas> </div> </div>
4. 初始化Avatar的JS脚本
if (typeof SpeechSDK === "undefined") { console.log("INFO FROM INDEX.HTML: ERROR-IN-SCRIPT: Speech SDK is not loaded.", "color: red; font-size: 16px;"); } else { window.speechConfig = SpeechSDK.SpeechConfig.fromSubscription("[SUBSCRIPTIONKEY]", "[REGION]"); window.avatarConfig = new SpeechSDK.AvatarConfig("lisa", "casual-sitting"); window.peerConnection = new RTCPeerConnection({ iceServers: [{ urls: ["[TURN-URL-AND-PORT]"], username: "[USERNAME]", credential: "[CREDENTIALS]" }] }); // Log connection state changes window.peerConnection.oniceconnectionstatechange = function () { console.log("INFO FROM INDEX.HTML: ICE Connection State: " + window.peerConnection.iceConnectionState, "color: orange; font-size: 14px;"); if (window.peerConnection.iceConnectionState === 'connected') { console.log("INFO FROM INDEX.HTML: TURN server connection established.", "color: green; font-size: 14px;"); } else if (window.peerConnection.iceConnectionState === 'failed') { console.error("ERROR FROM INDEX.HTML: TURN server connection failed.", "color: red; font-size: 14px;"); } }; window.peerConnection.ontrack = function (event) { console.log("INFO FROM INDEX.HTML: Track received: " + event.track.kind, "color: blue; font-size: 14px;"); if (event.track.kind === 'video') { const videoElement = document.createElement('video'); videoElement.srcObject = event.streams[0]; videoElement.autoplay = true; videoElement.muted = true; // Add this line to enable autoplay videoElement.playsInline = true; // For iOS videoElement.style.width = '100%'; window.remoteVideoDiv = document.getElementById('remoteVideo'); remoteVideoDiv.innerHTML = ''; remoteVideoDiv.appendChild(videoElement); console.info("INFO FROM INDEX.HTML: Video element appended.", "color: green; font-size: 12px;"); } if (event.track.kind === 'audio') { window.audioElement = document.createElement('audio'); audioElement.srcObject = event.streams[0]; audioElement.autoplay = true; audioElement.muted = true; document.body.appendChild(audioElement); console.info("INFO FROM INDEX.HTML: Audio element appended.", "color: green; font-size: 12px;"); } }; window.peerConnection.addTransceiver('video', { direction: 'sendrecv' }); window.peerConnection.addTransceiver('audio', { direction: 'sendrecv' }); window.avatarSynthesizer = new SpeechSDK.AvatarSynthesizer(window.speechConfig, window.avatarConfig); window.avatarSynthesizer.startAvatarAsync(window.peerConnection).then( () => { console.info("INFO FROM INDEX.HTML: Avatar started successfully.", "color: green; font-size: 14px;"); window.HybridWebView.InvokeDotNet('FinishInitializeAvatar'); // window.speak("Hello, this is a test message from your avatar.", "en"); } ).catch( (error) => { console.error("ERROR FROM INDEX.HTML: ERROR-IN-SCRIPT: Avatar failed to start. Error: " + error, "color: red; font-size: 16px;"); } ); }
5. 执行语音的JS函数
window.speak = (textToSpeech, detectedLanguage) => { const audioElement = document.getElementById('audio'); if (audioElement) { audioElement.muted = false; } const videoElement = document.getElementById('video'); if (videoElement) { videoElement.muted = false; } window.audioElement.muted = false; window.remoteVideoDiv.muted = false; let myMap = new Map([["de", "de-DE"], ["en", "en-GB"], ["fr", "fr-FR"], ["es", "es-ES"], ["pl", "pl-PL"], ["pt", "pt-PT"], ["tr", "tr-TR"], ["nl", "nl-NL"]]); let language = myMap.get(detectedLanguage); let spokenText = textToSpeech; let spokenSsml = `<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts' xml:lang="en-US"> <voice name='en-US-JennyMultilingualNeural'> <lang xml:lang='${language}'> ${htmlEncode(spokenText)} </lang> </voice> </speak>`; return window.avatarSynthesizer.speakSsmlAsync(spokenSsml).then( (result) => { if (result.reason === SpeechSDK.ResultReason.SynthesizingAudioCompleted) { console.log("INFO FROM INDEX.HTML: Speech synthesized successfully with text " + spokenText, "color: green; font-size: 12px;"); return "success"; } return "failed"; }) .catch((error) => { console.log("ERROR FROM INDEX.HTML: ERROR-IN-SCRIPT: Failed to synthesize speech. Error: " + error, "color: red; font-size: 16px;"); return "failed"; }); }
6. Android权限配置
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
已排查的措施
- 确认Android权限已配置齐全,包含
RECORD_AUDIO、MODIFY_AUDIO_SETTINGS等音频相关权限 - 在
speak函数中尝试多种方式解除元素静音,包括window.audioElement、remoteVideoDiv等 - 更换耳机测试,同时在Android模拟器及Pixel 8真机上验证问题
- 日志显示Avatar初始化成功,语音合成返回"success",ICE连接状态为connected
请求提供进一步的排查思路或解决方案。
内容的提问来源于stack exchange,提问作者Ricardo Enrico Jahn
相关产品推荐
相关产品推荐

