Uwp SpeechSynthesizer/MediaPlayer memory leak












0














We have a Uwp app that uses Microsoft voices to speak and read the text as it speaks. I noticed that the app's memory usage increases with each bit of text that is spoken, and it will eventually run out of memory. It does not matter which voice is used or what text is spoken.



In order to highlight the text, I subscribe to events in the TimedMedatataTracks of the MediaPlaybackItem. When the text is finished speaking, I unsubscribe each event and dispose the MediaPlaybackItem.Source. The Visual Studio memory profiler does not show any leaks in managed memory, so I suspect something is not getting cleaned up in the unmanaged space.



Edit: I commented on this in the code but I'll call it out here -- if I do not subscribe to the TimedMetadataTrack events, the leak goes away. I am also able to reproduce this using the Windows sample app (Synthesize Text with Boundaries)



Am I missing something that needs to be disposed, or is this a bug in SpeechSynthesizer/MediaPlayer?



using System;
using System.Diagnostics;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Media.SpeechSynthesis;

namespace WindowsTts
{
public class UwpNativeVoice : IDisposable
{
private readonly object _activeSpeechLock;
private SpeechSynthesizer _synthesizer;
private MediaPlayer _mediaPlayer;
private SpeechCallback _activeSpeech;

public UwpNativeVoice(VoiceInformation platformInfo)
{
_activeSpeechLock = new object();

_synthesizer = new SpeechSynthesizer();
_synthesizer.Options.IncludeWordBoundaryMetadata = true;
_synthesizer.Voice = platformInfo;

_mediaPlayer = new MediaPlayer
{
RealTimePlayback = true,
AutoPlay = false,
Volume = 1.0f
};
_mediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded += OnMediaPlayerMediaEnded;
}

public void Dispose()
{
_mediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded -= OnMediaPlayerMediaEnded;
(_mediaPlayer.Source as MediaPlaybackItem)?.Source?.Dispose();
_mediaPlayer.Source = null;
_mediaPlayer.Dispose();
_mediaPlayer = null;

_synthesizer?.Dispose();
_synthesizer = null;
}

public async void Speak(string text, SpeechDelegate speechDelegate)
{
if ( string.IsNullOrEmpty(text) )
{
// no-op; just fire events and bail
speechDelegate?.Invoke(text, ReadTextEvent.Start);
speechDelegate?.Invoke(text, ReadTextEvent.End);
return;
}

if (_activeSpeech != null)
{
// something currently speaking; halt it, fire events and then start anew
Halt();
}

// get synth stream, and add markers for bookmarks & word boundaries
var synthStream = await _synthesizer.SynthesizeTextToStreamAsync(text);

lock (_activeSpeechLock)
{
_activeSpeech = new SpeechCallback(text, speechDelegate);

try
{
var source = MediaSource.CreateFromStream(synthStream, synthStream.ContentType);
var playbackItem = new MediaPlaybackItem(source);
ConfigPlaybackEvents(playbackItem); //Comment this out and the leak goes away
_mediaPlayer.Source = playbackItem;
_mediaPlayer.Play();
}
catch (Exception e)
{
Debug.WriteLine(e);
_activeSpeech?.Invoke(ReadTextEvent.End);
_activeSpeech = null;
}
}
}

public bool Halt()
{
lock (_activeSpeechLock)
{
if (_activeSpeech == null)
return true;
}

_mediaPlayer.Pause();
DestroyMediaPlaybackItem(_mediaPlayer.Source as MediaPlaybackItem);
_mediaPlayer.Source = null;

SpeechCallback callback;
lock (_activeSpeechLock)
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

return true;
}

private void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
{
FireReadTextEvent(ReadTextEvent.Start);
}

private void OnTimedMetadataTrackEntered(TimedMetadataTrack track, MediaCueEventArgs args)
{
if ( track.TimedMetadataKind == TimedMetadataKind.Speech && args.Cue is SpeechCue speechCue )
{
var startIdx = speechCue.StartPositionInInput ?? 0;
var endIdx = speechCue.EndPositionInInput ?? -1;
FireReadTextEvent(ReadTextEvent.WordEvent(startIdx, (endIdx - startIdx) + 1));
}
}

private void OnMediaPlayerMediaEnded(MediaPlayer sender, object args)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

DestroyMediaPlaybackItem(sender.Source as MediaPlaybackItem);
sender.Source = null;
}

private void FireReadTextEvent(ReadTextEvent evt)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
callback = _activeSpeech;
callback?.Invoke(evt);
}

private void ConfigPlaybackEvents(MediaPlaybackItem playbackItem)
{
// see: https://docs.microsoft.com/en-us/uwp/api/windows.media.core.timedmetadatatrack

// iterate through existing tracks, registering callbacks for them
for ( int i = 0; i < playbackItem.TimedMetadataTracks.Count; i++ )
RegisterAction(playbackItem, i);
}

private void RegisterAction(MediaPlaybackItem item, int idx)
{
const string speechWordIdentifier = "SpeechWord";

TimedMetadataTrack track = item.TimedMetadataTracks[idx];
if (track.Id.Equals(speechWordIdentifier, StringComparison.Ordinal) || track.Label.Equals(speechWordIdentifier, StringComparison.Ordinal))
{
track.CueEntered += OnTimedMetadataTrackEntered;
item.TimedMetadataTracks.SetPresentationMode((uint)idx, TimedMetadataTrackPresentationMode.ApplicationPresented);
}
}

private void DestroyMediaPlaybackItem(MediaPlaybackItem item)
{
if ( item == null )
return;

foreach ( var track in item.TimedMetadataTracks )
{
track.CueEntered -= OnTimedMetadataTrackEntered;
}

item.Source?.Dispose();
}
}
}




namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}

namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}









share|improve this question
























  • Have you checked Halt method ? it does not work.
    – Nico Zhu - MSFT
    Nov 13 at 6:56










  • You could check this official code sample, and it have no memory leak issue from my test.
    – Nico Zhu - MSFT
    Nov 13 at 7:01










  • @NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
    – J Nelson
    Nov 13 at 12:29










  • @NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
    – J Nelson
    Nov 14 at 12:50


















0














We have a Uwp app that uses Microsoft voices to speak and read the text as it speaks. I noticed that the app's memory usage increases with each bit of text that is spoken, and it will eventually run out of memory. It does not matter which voice is used or what text is spoken.



In order to highlight the text, I subscribe to events in the TimedMedatataTracks of the MediaPlaybackItem. When the text is finished speaking, I unsubscribe each event and dispose the MediaPlaybackItem.Source. The Visual Studio memory profiler does not show any leaks in managed memory, so I suspect something is not getting cleaned up in the unmanaged space.



Edit: I commented on this in the code but I'll call it out here -- if I do not subscribe to the TimedMetadataTrack events, the leak goes away. I am also able to reproduce this using the Windows sample app (Synthesize Text with Boundaries)



Am I missing something that needs to be disposed, or is this a bug in SpeechSynthesizer/MediaPlayer?



using System;
using System.Diagnostics;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Media.SpeechSynthesis;

namespace WindowsTts
{
public class UwpNativeVoice : IDisposable
{
private readonly object _activeSpeechLock;
private SpeechSynthesizer _synthesizer;
private MediaPlayer _mediaPlayer;
private SpeechCallback _activeSpeech;

public UwpNativeVoice(VoiceInformation platformInfo)
{
_activeSpeechLock = new object();

_synthesizer = new SpeechSynthesizer();
_synthesizer.Options.IncludeWordBoundaryMetadata = true;
_synthesizer.Voice = platformInfo;

_mediaPlayer = new MediaPlayer
{
RealTimePlayback = true,
AutoPlay = false,
Volume = 1.0f
};
_mediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded += OnMediaPlayerMediaEnded;
}

public void Dispose()
{
_mediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded -= OnMediaPlayerMediaEnded;
(_mediaPlayer.Source as MediaPlaybackItem)?.Source?.Dispose();
_mediaPlayer.Source = null;
_mediaPlayer.Dispose();
_mediaPlayer = null;

_synthesizer?.Dispose();
_synthesizer = null;
}

public async void Speak(string text, SpeechDelegate speechDelegate)
{
if ( string.IsNullOrEmpty(text) )
{
// no-op; just fire events and bail
speechDelegate?.Invoke(text, ReadTextEvent.Start);
speechDelegate?.Invoke(text, ReadTextEvent.End);
return;
}

if (_activeSpeech != null)
{
// something currently speaking; halt it, fire events and then start anew
Halt();
}

// get synth stream, and add markers for bookmarks & word boundaries
var synthStream = await _synthesizer.SynthesizeTextToStreamAsync(text);

lock (_activeSpeechLock)
{
_activeSpeech = new SpeechCallback(text, speechDelegate);

try
{
var source = MediaSource.CreateFromStream(synthStream, synthStream.ContentType);
var playbackItem = new MediaPlaybackItem(source);
ConfigPlaybackEvents(playbackItem); //Comment this out and the leak goes away
_mediaPlayer.Source = playbackItem;
_mediaPlayer.Play();
}
catch (Exception e)
{
Debug.WriteLine(e);
_activeSpeech?.Invoke(ReadTextEvent.End);
_activeSpeech = null;
}
}
}

public bool Halt()
{
lock (_activeSpeechLock)
{
if (_activeSpeech == null)
return true;
}

_mediaPlayer.Pause();
DestroyMediaPlaybackItem(_mediaPlayer.Source as MediaPlaybackItem);
_mediaPlayer.Source = null;

SpeechCallback callback;
lock (_activeSpeechLock)
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

return true;
}

private void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
{
FireReadTextEvent(ReadTextEvent.Start);
}

private void OnTimedMetadataTrackEntered(TimedMetadataTrack track, MediaCueEventArgs args)
{
if ( track.TimedMetadataKind == TimedMetadataKind.Speech && args.Cue is SpeechCue speechCue )
{
var startIdx = speechCue.StartPositionInInput ?? 0;
var endIdx = speechCue.EndPositionInInput ?? -1;
FireReadTextEvent(ReadTextEvent.WordEvent(startIdx, (endIdx - startIdx) + 1));
}
}

private void OnMediaPlayerMediaEnded(MediaPlayer sender, object args)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

DestroyMediaPlaybackItem(sender.Source as MediaPlaybackItem);
sender.Source = null;
}

private void FireReadTextEvent(ReadTextEvent evt)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
callback = _activeSpeech;
callback?.Invoke(evt);
}

private void ConfigPlaybackEvents(MediaPlaybackItem playbackItem)
{
// see: https://docs.microsoft.com/en-us/uwp/api/windows.media.core.timedmetadatatrack

// iterate through existing tracks, registering callbacks for them
for ( int i = 0; i < playbackItem.TimedMetadataTracks.Count; i++ )
RegisterAction(playbackItem, i);
}

private void RegisterAction(MediaPlaybackItem item, int idx)
{
const string speechWordIdentifier = "SpeechWord";

TimedMetadataTrack track = item.TimedMetadataTracks[idx];
if (track.Id.Equals(speechWordIdentifier, StringComparison.Ordinal) || track.Label.Equals(speechWordIdentifier, StringComparison.Ordinal))
{
track.CueEntered += OnTimedMetadataTrackEntered;
item.TimedMetadataTracks.SetPresentationMode((uint)idx, TimedMetadataTrackPresentationMode.ApplicationPresented);
}
}

private void DestroyMediaPlaybackItem(MediaPlaybackItem item)
{
if ( item == null )
return;

foreach ( var track in item.TimedMetadataTracks )
{
track.CueEntered -= OnTimedMetadataTrackEntered;
}

item.Source?.Dispose();
}
}
}




namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}

namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}









share|improve this question
























  • Have you checked Halt method ? it does not work.
    – Nico Zhu - MSFT
    Nov 13 at 6:56










  • You could check this official code sample, and it have no memory leak issue from my test.
    – Nico Zhu - MSFT
    Nov 13 at 7:01










  • @NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
    – J Nelson
    Nov 13 at 12:29










  • @NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
    – J Nelson
    Nov 14 at 12:50
















0












0








0







We have a Uwp app that uses Microsoft voices to speak and read the text as it speaks. I noticed that the app's memory usage increases with each bit of text that is spoken, and it will eventually run out of memory. It does not matter which voice is used or what text is spoken.



In order to highlight the text, I subscribe to events in the TimedMedatataTracks of the MediaPlaybackItem. When the text is finished speaking, I unsubscribe each event and dispose the MediaPlaybackItem.Source. The Visual Studio memory profiler does not show any leaks in managed memory, so I suspect something is not getting cleaned up in the unmanaged space.



Edit: I commented on this in the code but I'll call it out here -- if I do not subscribe to the TimedMetadataTrack events, the leak goes away. I am also able to reproduce this using the Windows sample app (Synthesize Text with Boundaries)



Am I missing something that needs to be disposed, or is this a bug in SpeechSynthesizer/MediaPlayer?



using System;
using System.Diagnostics;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Media.SpeechSynthesis;

namespace WindowsTts
{
public class UwpNativeVoice : IDisposable
{
private readonly object _activeSpeechLock;
private SpeechSynthesizer _synthesizer;
private MediaPlayer _mediaPlayer;
private SpeechCallback _activeSpeech;

public UwpNativeVoice(VoiceInformation platformInfo)
{
_activeSpeechLock = new object();

_synthesizer = new SpeechSynthesizer();
_synthesizer.Options.IncludeWordBoundaryMetadata = true;
_synthesizer.Voice = platformInfo;

_mediaPlayer = new MediaPlayer
{
RealTimePlayback = true,
AutoPlay = false,
Volume = 1.0f
};
_mediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded += OnMediaPlayerMediaEnded;
}

public void Dispose()
{
_mediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded -= OnMediaPlayerMediaEnded;
(_mediaPlayer.Source as MediaPlaybackItem)?.Source?.Dispose();
_mediaPlayer.Source = null;
_mediaPlayer.Dispose();
_mediaPlayer = null;

_synthesizer?.Dispose();
_synthesizer = null;
}

public async void Speak(string text, SpeechDelegate speechDelegate)
{
if ( string.IsNullOrEmpty(text) )
{
// no-op; just fire events and bail
speechDelegate?.Invoke(text, ReadTextEvent.Start);
speechDelegate?.Invoke(text, ReadTextEvent.End);
return;
}

if (_activeSpeech != null)
{
// something currently speaking; halt it, fire events and then start anew
Halt();
}

// get synth stream, and add markers for bookmarks & word boundaries
var synthStream = await _synthesizer.SynthesizeTextToStreamAsync(text);

lock (_activeSpeechLock)
{
_activeSpeech = new SpeechCallback(text, speechDelegate);

try
{
var source = MediaSource.CreateFromStream(synthStream, synthStream.ContentType);
var playbackItem = new MediaPlaybackItem(source);
ConfigPlaybackEvents(playbackItem); //Comment this out and the leak goes away
_mediaPlayer.Source = playbackItem;
_mediaPlayer.Play();
}
catch (Exception e)
{
Debug.WriteLine(e);
_activeSpeech?.Invoke(ReadTextEvent.End);
_activeSpeech = null;
}
}
}

public bool Halt()
{
lock (_activeSpeechLock)
{
if (_activeSpeech == null)
return true;
}

_mediaPlayer.Pause();
DestroyMediaPlaybackItem(_mediaPlayer.Source as MediaPlaybackItem);
_mediaPlayer.Source = null;

SpeechCallback callback;
lock (_activeSpeechLock)
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

return true;
}

private void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
{
FireReadTextEvent(ReadTextEvent.Start);
}

private void OnTimedMetadataTrackEntered(TimedMetadataTrack track, MediaCueEventArgs args)
{
if ( track.TimedMetadataKind == TimedMetadataKind.Speech && args.Cue is SpeechCue speechCue )
{
var startIdx = speechCue.StartPositionInInput ?? 0;
var endIdx = speechCue.EndPositionInInput ?? -1;
FireReadTextEvent(ReadTextEvent.WordEvent(startIdx, (endIdx - startIdx) + 1));
}
}

private void OnMediaPlayerMediaEnded(MediaPlayer sender, object args)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

DestroyMediaPlaybackItem(sender.Source as MediaPlaybackItem);
sender.Source = null;
}

private void FireReadTextEvent(ReadTextEvent evt)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
callback = _activeSpeech;
callback?.Invoke(evt);
}

private void ConfigPlaybackEvents(MediaPlaybackItem playbackItem)
{
// see: https://docs.microsoft.com/en-us/uwp/api/windows.media.core.timedmetadatatrack

// iterate through existing tracks, registering callbacks for them
for ( int i = 0; i < playbackItem.TimedMetadataTracks.Count; i++ )
RegisterAction(playbackItem, i);
}

private void RegisterAction(MediaPlaybackItem item, int idx)
{
const string speechWordIdentifier = "SpeechWord";

TimedMetadataTrack track = item.TimedMetadataTracks[idx];
if (track.Id.Equals(speechWordIdentifier, StringComparison.Ordinal) || track.Label.Equals(speechWordIdentifier, StringComparison.Ordinal))
{
track.CueEntered += OnTimedMetadataTrackEntered;
item.TimedMetadataTracks.SetPresentationMode((uint)idx, TimedMetadataTrackPresentationMode.ApplicationPresented);
}
}

private void DestroyMediaPlaybackItem(MediaPlaybackItem item)
{
if ( item == null )
return;

foreach ( var track in item.TimedMetadataTracks )
{
track.CueEntered -= OnTimedMetadataTrackEntered;
}

item.Source?.Dispose();
}
}
}




namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}

namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}









share|improve this question















We have a Uwp app that uses Microsoft voices to speak and read the text as it speaks. I noticed that the app's memory usage increases with each bit of text that is spoken, and it will eventually run out of memory. It does not matter which voice is used or what text is spoken.



In order to highlight the text, I subscribe to events in the TimedMedatataTracks of the MediaPlaybackItem. When the text is finished speaking, I unsubscribe each event and dispose the MediaPlaybackItem.Source. The Visual Studio memory profiler does not show any leaks in managed memory, so I suspect something is not getting cleaned up in the unmanaged space.



Edit: I commented on this in the code but I'll call it out here -- if I do not subscribe to the TimedMetadataTrack events, the leak goes away. I am also able to reproduce this using the Windows sample app (Synthesize Text with Boundaries)



Am I missing something that needs to be disposed, or is this a bug in SpeechSynthesizer/MediaPlayer?



using System;
using System.Diagnostics;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Media.SpeechSynthesis;

namespace WindowsTts
{
public class UwpNativeVoice : IDisposable
{
private readonly object _activeSpeechLock;
private SpeechSynthesizer _synthesizer;
private MediaPlayer _mediaPlayer;
private SpeechCallback _activeSpeech;

public UwpNativeVoice(VoiceInformation platformInfo)
{
_activeSpeechLock = new object();

_synthesizer = new SpeechSynthesizer();
_synthesizer.Options.IncludeWordBoundaryMetadata = true;
_synthesizer.Voice = platformInfo;

_mediaPlayer = new MediaPlayer
{
RealTimePlayback = true,
AutoPlay = false,
Volume = 1.0f
};
_mediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded += OnMediaPlayerMediaEnded;
}

public void Dispose()
{
_mediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
_mediaPlayer.MediaEnded -= OnMediaPlayerMediaEnded;
(_mediaPlayer.Source as MediaPlaybackItem)?.Source?.Dispose();
_mediaPlayer.Source = null;
_mediaPlayer.Dispose();
_mediaPlayer = null;

_synthesizer?.Dispose();
_synthesizer = null;
}

public async void Speak(string text, SpeechDelegate speechDelegate)
{
if ( string.IsNullOrEmpty(text) )
{
// no-op; just fire events and bail
speechDelegate?.Invoke(text, ReadTextEvent.Start);
speechDelegate?.Invoke(text, ReadTextEvent.End);
return;
}

if (_activeSpeech != null)
{
// something currently speaking; halt it, fire events and then start anew
Halt();
}

// get synth stream, and add markers for bookmarks & word boundaries
var synthStream = await _synthesizer.SynthesizeTextToStreamAsync(text);

lock (_activeSpeechLock)
{
_activeSpeech = new SpeechCallback(text, speechDelegate);

try
{
var source = MediaSource.CreateFromStream(synthStream, synthStream.ContentType);
var playbackItem = new MediaPlaybackItem(source);
ConfigPlaybackEvents(playbackItem); //Comment this out and the leak goes away
_mediaPlayer.Source = playbackItem;
_mediaPlayer.Play();
}
catch (Exception e)
{
Debug.WriteLine(e);
_activeSpeech?.Invoke(ReadTextEvent.End);
_activeSpeech = null;
}
}
}

public bool Halt()
{
lock (_activeSpeechLock)
{
if (_activeSpeech == null)
return true;
}

_mediaPlayer.Pause();
DestroyMediaPlaybackItem(_mediaPlayer.Source as MediaPlaybackItem);
_mediaPlayer.Source = null;

SpeechCallback callback;
lock (_activeSpeechLock)
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

return true;
}

private void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
{
FireReadTextEvent(ReadTextEvent.Start);
}

private void OnTimedMetadataTrackEntered(TimedMetadataTrack track, MediaCueEventArgs args)
{
if ( track.TimedMetadataKind == TimedMetadataKind.Speech && args.Cue is SpeechCue speechCue )
{
var startIdx = speechCue.StartPositionInInput ?? 0;
var endIdx = speechCue.EndPositionInInput ?? -1;
FireReadTextEvent(ReadTextEvent.WordEvent(startIdx, (endIdx - startIdx) + 1));
}
}

private void OnMediaPlayerMediaEnded(MediaPlayer sender, object args)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
{
callback = _activeSpeech;
_activeSpeech = null;
}
callback?.Invoke(ReadTextEvent.End);

DestroyMediaPlaybackItem(sender.Source as MediaPlaybackItem);
sender.Source = null;
}

private void FireReadTextEvent(ReadTextEvent evt)
{
SpeechCallback callback;
lock ( _activeSpeechLock )
callback = _activeSpeech;
callback?.Invoke(evt);
}

private void ConfigPlaybackEvents(MediaPlaybackItem playbackItem)
{
// see: https://docs.microsoft.com/en-us/uwp/api/windows.media.core.timedmetadatatrack

// iterate through existing tracks, registering callbacks for them
for ( int i = 0; i < playbackItem.TimedMetadataTracks.Count; i++ )
RegisterAction(playbackItem, i);
}

private void RegisterAction(MediaPlaybackItem item, int idx)
{
const string speechWordIdentifier = "SpeechWord";

TimedMetadataTrack track = item.TimedMetadataTracks[idx];
if (track.Id.Equals(speechWordIdentifier, StringComparison.Ordinal) || track.Label.Equals(speechWordIdentifier, StringComparison.Ordinal))
{
track.CueEntered += OnTimedMetadataTrackEntered;
item.TimedMetadataTracks.SetPresentationMode((uint)idx, TimedMetadataTrackPresentationMode.ApplicationPresented);
}
}

private void DestroyMediaPlaybackItem(MediaPlaybackItem item)
{
if ( item == null )
return;

foreach ( var track in item.TimedMetadataTracks )
{
track.CueEntered -= OnTimedMetadataTrackEntered;
}

item.Source?.Dispose();
}
}
}




namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}

namespace WindowsTts
{
/// <summary>Defines a trigger that caused the broadcasting of a ReadTextEvent.</summary>
public enum ReadTextTrigger
{
Start,
Bookmark,
Word,
End,
}

/// <summary>A ReadTextEvent encompasses the relevant information from the tts world and is passed to the api user as part of a ReadTextInfo's EventAction data. </summary>
public class ReadTextEvent
{
public static ReadTextEvent Start { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.Start,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public static ReadTextEvent End { get; } = new ReadTextEvent()
{
Trigger = ReadTextTrigger.End,
BookmarkName = null,
TextOffset = -1,
TextLength = -1,
};

public ReadTextTrigger Trigger { get; set; }
public string BookmarkName { get; set; }
public int TextOffset { get; set; }
public int TextLength { get; set; }

/// <summary>Utility methods to pre-initialize some fields of this object.</summary>
public static ReadTextEvent Factory(ReadTextEvent src)
{
return new ReadTextEvent()
{
Trigger = src.Trigger,
BookmarkName = src.BookmarkName,
TextOffset = src.TextOffset,
TextLength = src.TextLength,
};
}

public static ReadTextEvent BookmarkEvent(string bookmark)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Bookmark,
BookmarkName = bookmark,
TextOffset = -1,
TextLength = -1,
};
}

public static ReadTextEvent WordEvent(int textOffset, int textLength)
{
return new ReadTextEvent()
{
Trigger = ReadTextTrigger.Word,
BookmarkName = null,
TextOffset = textOffset,
TextLength = textLength,
};
}

private ReadTextEvent()
{
}
}

/// <summary>
/// A SpeechDelegate is passed to the ITtsVoice.Speak() method, so that the caller may receive progress info as the text is being spoken.
/// </summary>
/// <param name="speechText"></param>
/// <param name="readTextEvent"></param>
public delegate void SpeechDelegate(string speechText, ReadTextEvent readTextEvent);

/// <summary>
/// This class encapsulates everything necessary to invoke a SpeechDelegate.
/// A SpeechCallback instance may be created each time a new string is enqueued for speaking,
/// and then invoked multiple times throughout the process, with an updated ReadTextEvent.
/// </summary>
public class SpeechCallback
{
private readonly SpeechDelegate _speechDelegate;

public SpeechCallback(string text, SpeechDelegate speechDelegate)
{
Text = text;
_speechDelegate = speechDelegate;
}

public string Text { get; }

public void Invoke(ReadTextEvent readTextEvent) => _speechDelegate?.Invoke(Text, readTextEvent);
}
}






memory uwp media-player text-to-speech speechsynthesizer






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 13 at 12:35

























asked Nov 12 at 16:40









J Nelson

417




417












  • Have you checked Halt method ? it does not work.
    – Nico Zhu - MSFT
    Nov 13 at 6:56










  • You could check this official code sample, and it have no memory leak issue from my test.
    – Nico Zhu - MSFT
    Nov 13 at 7:01










  • @NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
    – J Nelson
    Nov 13 at 12:29










  • @NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
    – J Nelson
    Nov 14 at 12:50




















  • Have you checked Halt method ? it does not work.
    – Nico Zhu - MSFT
    Nov 13 at 6:56










  • You could check this official code sample, and it have no memory leak issue from my test.
    – Nico Zhu - MSFT
    Nov 13 at 7:01










  • @NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
    – J Nelson
    Nov 13 at 12:29










  • @NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
    – J Nelson
    Nov 14 at 12:50


















Have you checked Halt method ? it does not work.
– Nico Zhu - MSFT
Nov 13 at 6:56




Have you checked Halt method ? it does not work.
– Nico Zhu - MSFT
Nov 13 at 6:56












You could check this official code sample, and it have no memory leak issue from my test.
– Nico Zhu - MSFT
Nov 13 at 7:01




You could check this official code sample, and it have no memory leak issue from my test.
– Nico Zhu - MSFT
Nov 13 at 7:01












@NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
– J Nelson
Nov 13 at 12:29




@NicoZhu-MSFT thanks, that's a good idea. However, I am still seeing a leak with that sample in the second scenario (Synthesize text with boundaries). Open the sample app side by side with task manager. Note the current memory usage. Use some short text in (e.g. "Sample text"), then press speak several times. I see the memory usage increasing several MB and it never returns to the baseline. I am confident the issue is in the metadata CueEntered events. If I comment those out of my code, it does not leak.
– J Nelson
Nov 13 at 12:29












@NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
– J Nelson
Nov 14 at 12:50






@NicoZhu-MSFT no, I haven't. Sorry, let me clarify: Removing the CueEntered events does fix the memory leak, meaning the leak must be related to those events. However, I need to subscribe to those events in order to highlight text in my app, so removing them is not a solution.
– J Nelson
Nov 14 at 12:50



















active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53266509%2fuwp-speechsynthesizer-mediaplayer-memory-leak%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown






























active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53266509%2fuwp-speechsynthesizer-mediaplayer-memory-leak%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Xamarin.iOS Cant Deploy on Iphone

Glorious Revolution

Dulmage-Mendelsohn matrix decomposition in Python