I'm building an app with optional audio support, and it will feature some kind of background sounds (like a looping soundtrack that keeps on playing in every page).
What I did is to create an audio manager that allows me to manage my sounds, and this should mute/unmute the audio based on user's settings.
Unfortunately this doesn't happen, and the audio keeps on playing even if the user disables it.
This is a sample of my code:
public static async Task StartSoundManager()
{
// Get audio stream from app folder
// ...
BackgroundSound.SetSource(currentStream, currentFile.ContentType);
BackgroundSound.IsLooping = true;
ToggleSounds();
}
public static void ToggleSounds()
{
BackgroundSound.IsMuted = !Settings.IsAudioOn;
}
public bool IsAudioOn
{
// standard getter
set
{
// save value
SoundManager.ToggleSounds();
}
}
After some tests, IsMuted is set correctly (I've also tried setting volume to 0) but nothing happens when changing settings.
Do you guys have any idea on why such a simple task is not working as expected? It seems to me that you can't change volume after setting the source, and this feels really wrong.
EDIT: more complete class
public static class AudioManager
{
public const string BACKGROUND = "BACKGROUND.mp3";
private static readonly MediaElement BackgroundSound = new MediaElement();
public static async Task StartSoundManager()
{
// Get folder
var folder =
await (await Package.Current.InstalledLocation.GetFolderAsync("Assets")).GetFolderAsync("Audio");
var currentFile = await folder.GetFileAsync(BACKGROUND);
var currentStream = await currentFile.OpenAsync(FileAccessMode.Read);
BackgroundSound.SetSource(currentStream, currentFile.ContentType);
// Set mode and volume
BackgroundSound.IsLooping = true;
ToggleSounds();
}
public static void ToggleSounds()
{
BackgroundSound.IsMuted = !Settings.IsAudioOn; // IsAudioOn is false, still the sound plays
}
}
MediaElement is a XAML control, to make MediaElement.IsMuted property work, we need to add MediaElement into Visual Tree. For example, in your code, we can change BackgroundSound to a public field like:
public static readonly MediaElement BackgroundSound = new MediaElement();
And then in a page (e.g. MainPage) add it to the page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
await AudioManager.StartSoundManager();
rootGrid.Children.Add(AudioManager.BackgroundSound);
}
After this, your ToggleSounds method should be able to work.
But since you want to keep on playing in every page, add MediaElement into the page may be not a good practice, here I'd suggest you use MediaPlayer class instead of MediaElement like:
public static class AudioManager
{
public const string BACKGROUND = "BACKGROUND.mp3";
private static readonly MediaPlayer BackgroundSound = new MediaPlayer();
public static void StartSoundManager()
{
BackgroundSound.Source = MediaSource.CreateFromUri(new Uri($"ms-appx:///Assets/Audio/{BACKGROUND}"));
BackgroundSound.IsLoopingEnabled = true;
ToggleSounds();
BackgroundSound.Play();
}
public static void ToggleSounds()
{
BackgroundSound.IsMuted = !Settings.IsAudioOn; // IsAudioOn is false, still the sound plays
}
}
For more info, please see Play audio and video with MediaPlayer.
I have a similar situation. I have a ToggleMenuFlyoutItem for turning sounds on and off app-wide that writes to the local settings:
Here is the XAML:
<Button IsTabStop="False" Style="{StaticResource qButtonStyleFinal}" TabIndex="2" x:Name="btnMore" BorderBrush="{x:Null}" Foreground="{x:Null}"
Content="1" Grid.Column="37" Margin="5" HorizontalAlignment="Center" Grid.Row="1" Grid.RowSpan="4" Grid.ColumnSpan="2" MaxWidth="60" MaxHeight="60">
<Button.Background>
<ImageBrush ImageSource="ms-appx:///Assets/more_project.png" Stretch="Uniform" />
</Button.Background>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem x:Name="mfiToggleSounds" Text="sound effects" IsChecked="{x:Bind Mode=TwoWay, Path=Model.Sounds}"></ToggleMenuFlyoutItem>
<MenuFlyoutItem x:Name="mfiExportExcel" Text="export to Excel" Tapped="mfiExportExcel_Tapped" />
<MenuFlyoutItem x:Name="mfiExportCSV" Text="export to CSV" Tapped="mfiExportCSV_Tapped" />
<MenuFlyoutItem x:Name="mfiEmailDeveloper" Text="email developer" Tapped="mfiEmailDeveloper_Tapped" />
</MenuFlyout>
</Button.Flyout>
</Button>
Changes are handled in the property set event:
private bool _sounds = true;
public bool Sounds
{
get => _sounds;
set
{
_sounds = value;
NotifyPropertyChanged();
var localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["sounds"] = _sounds;
}
}
Setting is loaded on the main page Page_Loaded event as follows:
// retrieve settings
var localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values.ContainsKey("sounds"))
{
clsGlobal.statModel.Sounds = (bool)localSettings.Values["sounds"];
}
And when I play a sound, a simple if condition is included:
if (clsGlobal.statModel.Sounds && clsGlobal.fileInputs.ContainsKey("problem.wav"))
{
var snd1 = clsGlobal.fileInputs["problem.wav"];
if (snd1 != null)
{
snd1.Reset();
snd1.Start();
}
}
I am using the AudioGraph API for my sounds. Works better than the MediaPlayer route since that was giving me a weird situation where the sounds would not play sometimes on the first request. AudioGraph works better for me and is really fast to play sounds.
Related
Disclaimer: I am a newbie to c# and Xamarin.Forms - sorry for missing anything obvious.
I am trying to create an app that takes user input in the form of a voice command (using Speech-To-Text) and outputs an audio announcement from the application (using Text-To-Speech).
The issue is that when you start recording audio for the Speech-To-Text service, the device's audio is set to recording mode (not sure what the technical term for this is called) and playback audio is set to a very low volume (as described in this SO question and here) and here.
I'm ideally looking for a way to revert this so that once the appropriate voice command is recognised (i.e. 'Secret command') via Speech-To-Text, the user can hear the secret phrase back at full/normal volume through Text-To-Speech in a Xamarin Forms application.
I tried to produce a working example by adapting the sample code for Azure Cognitive Speech Service. I cloned the code and adapted the Xaml and CS for the MainPage slightly, as shown below, to stop the speech recognition service once a certain voice command is triggered and then activate a phrase to be spoken via the Text-To-Speech service. My sample demonstrates the issue. If the user starts by selecting the Transcribe button and enters the appropriate voice command, they should hear back the secret phrase, but the playback volume is so low when testing on a physical IOS device I can barely hear it.
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="CognitiveSpeechService.MyPage"
Title="Speech Services Transcription"
Padding="10,35,10,10">
<StackLayout>
<Frame BorderColor="DarkGray"
CornerRadius="10"
HeightRequest="300"
WidthRequest="280"
HorizontalOptions="Center"
VerticalOptions="Start"
BackgroundColor="LightGray">
<ScrollView x:Name="scroll">
<Label x:Name="transcribedText"
Margin="10,10,10,10" />
</ScrollView>
</Frame>
<ActivityIndicator x:Name="transcribingIndicator"
HorizontalOptions="Center"
VerticalOptions="Start"
WidthRequest="300"
IsRunning="False" />
<Button x:Name="transcribeButton"
WidthRequest="300"
HeightRequest="50"
Text="Transcribe"
TextColor="White"
CornerRadius="10"
BackgroundColor="Green"
BorderColor="DarkGray"
BorderWidth="1"
FontAttributes="Bold"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="TranscribeClicked"/>
<Button x:Name="SpeakBtn"
WidthRequest="300"
HeightRequest="50"
Text="Speak"
TextColor="White"
CornerRadius="10"
BackgroundColor="Red"
BorderColor="DarkGray"
BorderWidth="1"
FontAttributes="Bold"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="SpeakBtn_Clicked"/>
</StackLayout>
</ContentPage>
Code-behind
namespace CognitiveSpeechService
{
public partial class MyPage : ContentPage
{
AudioRecorderService recorder = new AudioRecorderService();
SpeechRecognizer recognizer;
IMicrophoneService micService;
bool isTranscribing = false;
public MyPage()
{
InitializeComponent();
micService = DependencyService.Resolve<IMicrophoneService>();
}
async void TranscribeClicked(object sender, EventArgs e)
{
bool isMicEnabled = await micService.GetPermissionAsync();
// EARLY OUT: make sure mic is accessible
if (!isMicEnabled)
{
UpdateTranscription("Please grant access to the microphone!");
return;
}
// initialize speech recognizer
if (recognizer == null)
{
var config = SpeechConfig.FromSubscription(Constants.CognitiveServicesApiKey, Constants.CognitiveServicesRegion);
recognizer = new SpeechRecognizer(config);
recognizer.Recognized += (obj, args) =>
{
UpdateTranscription(args.Result.Text);
};
}
// if already transcribing, stop speech recognizer
if (isTranscribing)
{
StopSpeechRecognition();
}
// if not transcribing, start speech recognizer
else
{
Device.BeginInvokeOnMainThread(() =>
{
InsertDateTimeRecord();
});
try
{
await recognizer.StartContinuousRecognitionAsync();
}
catch (Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = true;
}
UpdateDisplayState();
}
// https://stackoverflow.com/questions/56514413/volume-has-dropped-significantly-in-text-to-speech-since-adding-speech-to-text
private async void StopSpeechRecognition()
{
if (recognizer != null)
{
try
{
await recognizer.StopContinuousRecognitionAsync();
Console.WriteLine($"IsRecording: {recorder.IsRecording}");
}
catch (Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = false;
UpdateDisplayState();
}
}
void UpdateTranscription(string newText)
{
Device.BeginInvokeOnMainThread(() =>
{
if (!string.IsNullOrWhiteSpace(newText))
{
if (newText.ToLower().Contains("Secret command"))
{
Console.WriteLine("heart rate voice command detected");
// stop speech recognition
StopSpeechRecognition();
// do callout
string success = "this works!";
var settings = new SpeechOptions()
{
Volume = 1.0f,
};
TextToSpeech.SpeakAsync(success, settings);
// start speech recongition
} else
{
transcribedText.Text += $"{newText}\n";
}
}
});
}
void InsertDateTimeRecord()
{
var msg = $"=================\n{DateTime.Now.ToString()}\n=================";
UpdateTranscription(msg);
}
void UpdateDisplayState()
{
Device.BeginInvokeOnMainThread(() =>
{
if (isTranscribing)
{
transcribeButton.Text = "Stop";
transcribeButton.BackgroundColor = Color.Red;
transcribingIndicator.IsRunning = true;
}
else
{
transcribeButton.Text = "Transcribe";
transcribeButton.BackgroundColor = Color.Green;
transcribingIndicator.IsRunning = false;
}
});
}
async void SpeakBtn_Clicked(object sender, EventArgs e)
{
await TextToSpeech.SpeakAsync("Sample audio line. Blah blah blah. ");
}
}
}
Thanks for your help!
Found a working solution. Posting it below for whoever else it can help and future me.
I noticed this issue was only happening on IOS and not Android, it has to do with the category that AVAudioSession is set to when STT is enabled. As I best understand it, once STT is enabled, audio-ducking turns on for any non-STT-related audio.
You can resolve this issue by programmatically setting the right category using the AVAudioSession Xamarin.IOS API.
To get this working properly in a Xamarin.Forms project, you will need to use the Dependency Service to execute the Xamarin.IOS code in your shared project code.
I have set out the relevant bits of the code that worked for me below.
A full working example can be found in the solution branch of the Github repo mentioned in the comments above.
Mainpage (where STT and TTS services are happening)
public partial class MainPage : ContentPage
{
IAudioSessionService audioService;
public MainPage()
{
InitializeComponent();
micService = DependencyService.Resolve<IMicrophoneService>();
if (Device.RuntimePlatform == Device.iOS)
{
audioService = DependencyService.Resolve<IAudioSessionService>();
}
}
public void SpeechToText()
{
// wherever STT is required, call this first to set the right audio category
audioService?.ActivateAudioRecordingSession();
}
public void TextToSpeech()
{
// wherever TTS is required, let the OS know that you're playing audio so TTS interrupts instead of ducking.
audioService?.ActivateAudioPlaybackSession();
await TextToSpeech.SpeakAsync(TextForTextToSpeechAfterSpeechToText, settings);
// set audio session back to recording mode ready for STT
audioService?.ActivateAudioRecordingSession();
}
IAudioSessionService
// this interface should be in your shared project
namespace CognitiveSpeechService.Services
{
public interface IAudioSessionService
{
void ActivateAudioPlaybackSession();
void ActivateAudioRecordingSession();
}
}
project.Android/AndroidAudioSessionService
using System;
using CognitiveSpeechService.Services;
using Xamarin.Forms;
[assembly: Dependency(typeof(CognitiveSpeechService.Droid.Services.AndroidAudioSessionService))]
namespace CognitiveSpeechService.Droid.Services
{
public class AndroidAudioSessionService : IAudioSessionService
{
public void ActivateAudioPlaybackSession()
{
// do nothing as not required on Android
}
public void ActivateAudioRecordingSession()
{
// do nothing as not required on Android
}
}
}
Project.iOS/IOSAudioSessionService
using System;
using AVFoundation;
using CognitiveSpeechService.Services;
using Foundation;
using Xamarin.Forms;
[assembly: Dependency(typeof(CognitiveSpeechService.iOS.Services.IOSAudioSessionService))]
namespace CognitiveSpeechService.iOS.Services
{
public class IOSAudioSessionService : IAudioSessionService
{
public void ActivateAudioPlaybackSession()
{
var session = AVAudioSession.SharedInstance();
session.SetCategory(AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.DuckOthers);
session.SetMode(AVAudioSession.ModeSpokenAudio, out NSError error);
session.SetActive(true);
}
public void ActivateAudioRecordingSession()
{
try
{
new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
var session = AVAudioSession.SharedInstance();
session.SetCategory(AVAudioSessionCategory.Record);
session.SetActive(true);
})).Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
I'm running into a peculiar issue, where data-binding two text boxes to the same object works just fine, but after I load data back into my program via XML deserialization the binding fails on one of those text boxes.
I use a splitview to build a little hamburger navigation menu and load my pages into a frame
Here is the related XAML in my MainPage.xaml
<TextBlock Name="PlayerGoldTextBlock"
RelativePanel.RightOf="PlayerNameTextBlock"
RelativePanel.AlignVerticalCenterWithPanel="True"
Foreground="Gold"
FontSize="28"
Text="{x:Bind myGame.Game.Player.Gold, Mode=OneWay}" />
<TextBlock Name="RemainingTurnsTextBlock"
RelativePanel.RightOf="PlayerGoldTextBlock"
RelativePanel.AlignVerticalCenterWithPanel="True"
Foreground="Red"
Text="{x:Bind myGame.Game.RemainingTurns, Mode=OneWay}" />
<SplitView.Content>
<Frame Name="MyFrame"></Frame>
</SplitView.Content>
In the page I have loaded into the frame:
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Gold:" HorizontalAlignment="Left" FontSize="16" Margin="0,0,10,0"/>
<TextBlock Text="{x:Bind myGame.Game.Player.Gold, Mode=OneWay}" HorizontalAlignment="Left" FontSize="16" Margin="0,0,10,0"/>
</StackPanel>
in MainPage: The PlayerGoldTextBlock fails to bind correctly after I load, but the RemainingTurnsTextBlock works fine
in my frame: x:Bind myGame.Game.Player.Gold seems to rebind just fine after I load.
Another thing, which may be related, is that upon loading I see the RemainingTurnsTextBlock update immediately, but the (working) player gold in the frame doesn't until I navigate to a different frame and then back
here is my deserialization code, in case that matters:
async public void LoadGame()
{
var filename = "testsave.xml";
var serializer = new XmlSerializer(typeof(GameModel));
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.GetFileAsync(filename).AsTask().ConfigureAwait(false);
Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false);
GameModel myGame;
using (stream)
{
myGame = (GameModel)serializer.Deserialize(stream);
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Player = myGame.Player;
this.Locations = myGame.Locations;
this.RemainingTurns = myGame.RemainingTurns;
});
}
}
here are some of the properties from my GameModel
public Player Player { get; set; }
public int MaxTurns { get; set; }
private int remainingTurns;
public int RemainingTurns
{
get { return remainingTurns; }
set { SetProperty(remainingTurns, value, () => remainingTurns = value);}
}
here is the gold property in player
private int gold;
public int Gold
{
get { return gold; }
set {SetProperty(gold, value, () => gold = value);}
}
here is setproperty
protected bool SetProperty<T>(T currentValue, T newValue, Action DoSet,
[CallerMemberName] String property = null)
{
if (EqualityComparer<T>.Default.Equals(currentValue, newValue)) return false;
DoSet?.Invoke();
RaisePropertyChanged(property);
return true;
}
Edit to clarify problem statement based on XY problem comment:
I have things bound to Game.Player.Gold
On launch they work fine
After doing XML deserialization and loading a saved state, some of the binding works when I navigate away and come back, but some dies completely
Binding doesn't seem to like when I change the player instance or something, because it can't drill down into where the gold property is. Its weird to me that one textbox in the frame can rebind after i navigate, but one in the mainwindow can't
I found a fix for this, using a setter on Game.Player directly, in addition to Game.Player.Gold, but I see some similar problems cropping up for other properties.
Do I have some misunderstanding of how these things see one another?
You are not updating myGame.Game.Player.Gold live, so it will fetch the details from the Player class when the page reloads for the frame, but is missing the details to update it instantly upon change as RemainingTurns does. There may be an issue with accessors between the Frame and the Main Window.
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Player = myGame.Player;
this.Locations = myGame.Locations;
this.RemainingTurns = myGame.RemainingTurns;
// You need to get the value for Player.Gold
});
I solved this by adding a Setter to the Player property in my Game
public Player Player
{
get { return player; }
set { SetProperty(player, value, () => player = value); }
}
This seems to fire off an event for anything subscribing to the properties of player that the player has been changed.
However, it didn't fix all the databinding issues I was seeing post load for different Player properties. For example, an ObservableCollection property still doesn't seem to bind right until I navigate frames.
It works fine when I start the game, but after I load in data from XML it still requires a navigation to populate correctly even with this fix added in.
I'm not sure if this is the right fix and I have a second issue or if this fix is just a weird bandaide for part of the issue. It might be a misunderstanding on the databinding aspect or on the deserialization aspect that I'm not getting.
I'm learning Universal Windows Platform and I'm currently analysing music player using SoundCloud API from this site. Link to the github project is at the very bottom of the page. To get this project to work variable SoundCloudClientId from App.xaml.cs should be filled in (I used client id from previous example on the same page, not sure if I can paste that).
When application starts the NowPlaying page is loaded and changing tracks causes UI to update accordingly. The problem is when I navigate to any other page and return back to NowPlaying. I can still change music using buttons, but UI doesn't change (song title, album title etc.).
Important parts of the code:
NowPlaying.xaml
<ImageBrush x:Name="albumrtImage" ImageSource="Assets\Albumart.png" Stretch="UniformToFill" />
<TextBlock x:Name="txtSongTitle" Grid.Row="0" HorizontalAlignment="Center" Text="Song Title " FontSize="25" Foreground="White" Style="{StaticResource HeaderTextBlockStyle}" TextTrimming="WordEllipsis" />
<TextBlock x:Name="txtAlbumTitle" Grid.Row="0" Text="Label " HorizontalAlignment="Center" FontWeight="Light" FontSize="20" Foreground="#9799a5" Style="{StaticResource BodyTextBlockStyle}" TextTrimming="WordEllipsis"/>
NowPlaying.xaml.cs
async void BackgroundMediaPlayer_MessageReceivedFromBackground(object sender, MediaPlayerDataReceivedEventArgs e)
{
TrackChangedMessage trackChangedMessage;
if (MessageService.TryParseMessage(e.Data, out trackChangedMessage))
{
// When foreground app is active change track based on background message
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var songIndex = GetSongIndexById(trackChangedMessage.TrackId);
if (songIndex >= 0)
{
var song = App.likes[songIndex];
LoadTrack(song); //Update UI
}
});
return;
}
BackgroundAudioTaskStartedMessage backgroundAudioTaskStartedMessage;
if (MessageService.TryParseMessage(e.Data, out backgroundAudioTaskStartedMessage))
{
backgroundAudioTaskStarted.Set();
return;
}
}
private async void LoadTrack(SoundCloudTrack currentTrack)
{
try
{
//Change album art
string albumartImage = Convert.ToString(currentTrack.artwork_url);
if (string.IsNullOrWhiteSpace(albumartImage))
{
albumartImage = #"ms-appx:///Assets/Albumart.png";
}
else
{
albumartImage = albumartImage.Replace("-large", "-t500x500");
}
//Next 3 lines when pages were switched don't cause UI to update
albumrtImage.ImageSource = new BitmapImage(new Uri(albumartImage));
txtSongTitle.Text = currentTrack.title;
txtAlbumTitle.Text = Convert.ToString(currentTrack.user.username);
}
catch (Exception ex)
{
MessageDialog showMessgae = new MessageDialog("Something went wrong. Please try again. Error Details : " + ex.Message);
await showMessgae.ShowAsync();
}
}
After navigating from NowPlaying->Me->NowPlaying and clicking next the track changes, but UI doesn't update as seen on the screen below:
UI problem
I'm trying to reproduce the problem on a simple example, but without any luck. What could cause this issue? Any help is appreciated.
I've found the solution. The problem was with cache. The NowPlaying page required putting following property:
NavigationCacheMode="Required"
I just want to add that you can also check navigation mode in OnNavigatedTo method. It can be helpful when for instance you would like to refresh the data when returning to the selected page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(e.NavigationMode == NavigationMode.Back)
{
//refresh the data here...
}
}
This is a problem that has been bugging me a lot. I still haven't got a solution for this.
I have this XAML code, the DrawingSurfaceBackgroundGrid is an Unity game, and the rest is collapsed Camera.
<DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
<!-- Augmented Reality -->
<Canvas VerticalAlignment="Center" x:Name="arCameraStack" Canvas.ZIndex="1" Width="732" Height="549" HorizontalAlignment="Left" Visibility="Collapsed" Tap="viewfinderCanvas_Tap">
<Canvas.Background>
<VideoBrush x:Name="viewfinderBrush" />
</Canvas.Background>
<Image x:Name="imgTarget" Source="/Assets/Icons/camera.target.png" VerticalAlignment="Center" HorizontalAlignment="Center" Canvas.Left="114" Canvas.Top="27"/>
</Canvas>
</DrawingSurfaceBackgroundGrid>
These are my Start/Stop camera functions:
public void StartCamera(bool hasTarget)
{
camera = new PhotoCamera(CameraType.Primary);
viewfinderBrush.SetSource(camera);
Dispatcher.BeginInvoke(() =>
{
if (hasTarget)
{
imgTarget.Visibility = Visibility.Visible;
}
else
{
imgTarget.Visibility = Visibility.Collapsed;
}
});
}
public void StopCamera()
{
if (camera != null)
{
camera.Dispose();
camera = null;
}
}
I have a screen system that changes the views according to what you want to see, sorta like changing pages.
This gets called in the page that shows the Camera part.
public void Show()
{
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.arCameraStack.Visibility = Visibility.Visible;
});
MainPage.Instance.StartCamera(false);
}
And this gets called when I want to hide the Unity part.
public void Hide()
{
UnityApp.SetNativeResolution(0, 0);
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
Everything works when I have the solution build and run through Visual Studio, but once I start the app from the phone, not in Master/Debug mode from VS, the camera just hangs. I found out that when I toggle the visibility of anything in the page, the camera will update for a second, and then hang once again.
Well, I found out what was the problem.
My Show/Hide functions look like these now:
public void Hide()
{
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(null);
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(null);
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Visible;
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
public void Show()
{
var content = Application.Current.Host.Content;
var width = (int)Math.Floor(content.ActualWidth * content.ScaleFactor / 100.0 + 0.5);
var height = (int)Math.Floor(content.ActualHeight * content.ScaleFactor / 100.0 + 0.5);
UnityApp.SetNativeResolution(width, height);
UnityApp.UnObscure();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(UnityApp.GetBackgroundContentProvider());
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(UnityApp.GetManipulationHandler());
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Collapsed;
MainPage.Instance.ApplicationBar.IsVisible = false;
});
}
The main problem was in the DrawingSurfaceBackground.SetBackgroundContentProvider and DrawingSurfaceBackground.SetBackgroundManiuplationHandler functions.
Now switching between the Unity part and WP part is easy.
I want to play some audio in the background on my Windows Phone. I've written some code like this sample from Microsoft (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202978(v=vs.105).aspx), but on my app the user has the opportunity to select a uri which the background agent has to play. But I don't know how I can set the audiotrack element from my app to the audiotrack element of the background agent.
I've tried the following code in my agent:
private static AudioTrack _streamTrack;
public static AudioTrack StreamTrack { get { return _streamTrack; } set { _streamTrack = value; } }
And try to set this variable in my app like:
AudioPlayer.StreamTrack = new AudioTrack(new Uri(stream.StreamUri, UriKind.Absolute), stream.StreamName, stream.StreamGenre, stream.StreamGenre, null);
But it doesn't work. How can I fix this problem?
One way to accomplish this is to use the XNA library
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
Then declare your soundeffect
SoundEffect _BGMUSIC;
I use this method of loading sound effects
//Put this in your main method
LoadSound("sfx/piano.wav", out _BGMUSIC);
//put this method in the same class
private void LoadSound(String SoundFilePath, out SoundEffect Sound)
{
// For error checking, assume we'll fail to load the file.
Sound = null;
try
{
// Holds informations about a file stream.
StreamResourceInfo SoundFileInfo = App.GetResourceStream(new Uri(SoundFilePath, UriKind.Relative));
// Create the SoundEffect from the Stream
Sound = SoundEffect.FromStream(SoundFileInfo.Stream);
FrameworkDispatcher.Update();
}
catch (NullReferenceException)
{
// Display an error message
MessageBox.Show("Couldn't load sound " + SoundFilePath);
}
}
Finally you can play your sound effect
_BGMUSIC.Play();
You should only set the url to BackgroundAudioPlayer.Instance.Track.
Source code
XAML
<StackPanel Orientation="Vertical">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Enter url into textbox" />
<TextBox Name="fileUrl" />
<Button Content=">"
Height="100"
Width="100"
Click="playCustomFile_Click" />
</StackPanel>
CS
private void playCustomFile_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(fileUrl.Text.Trim().ToString()))
MessageBox.Show("Please enter url first");
else
BackgroundAudioPlayer.Instance.Track = new AudioTrack(new Uri(fileUrl.Text.Trim().ToString(), UriKind.Absolute), "title","artist","album", new Uri("albumArtUrl",UriKind.RelativeOrAbsolute));
}