How to release memory used by MediaElement - c#

I am using MediaElement to show video clips in a loop for long period of time. After some time (hours for Win 7 / 4 GB RAM) the program crashes with exception of type "Insufficient memory". I have monitored the memory used while playing with Process Explorer-Sysinternals and also logged it using System.Diagnostics.Process methods. Both ways show gradually increasing of used memory.
Here is the code:
XAML:
<Grid Name="GridTest">
<MediaElement x:Name="MediaPlayer"
LoadedBehavior="Manual"
MediaEnded="VideoControl_MediaEnded"
MediaOpened="MediaPlayer_MediaOpened"
Source="{Binding Mode=OneWay,
Path=MySource}" />
</Grid>
.cs:
public partial class MainWindow : Window
{
public MainViewModel model = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.GridTest.DataContext = model;
// fill in model.MediaFilesUris:
...
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
MediaPlayer.Play();
}
private void VideoControl_MediaEnded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
model.OnPropertyChanged("MySource");
MediaPlayer.Play();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public Uri[] MediaFilesUris = null;
public int crn = 0;
public Uri MySource { get { if (MediaFilesUris != null && MediaFilesUris.Count()>0) return MediaFilesUris[crn]; else return null; } }
}
I have also tested the case when MediaElement object is created dynamically, destroyed (together with all unsubscribing from events, etc.) after several clips and created again. Memory got consumed increasingly again.
Any suggestions would be appreciated!

Try to specify MediaElement UnloadingBehavior="Close"property in your XAML.
According to MSDN MediaState::Close indicates that
All media resources are released (including video memory).

My proposal is to make the following:
private void VideoControl_MediaEnded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
//make the following explicitly
MediaPlayer.Stop();
MediaPlayer.Source = null;
model.OnPropertyChanged("MySource");
MediaPlayer.Play();
}

Related

UWP application hangs when using multiple Webviews with heavy URLs

I have to rotate between URLs(lets say 10 urls). Every url has its own Webview and each webview is shown for 15 secs(one at a time). I can change the urls from the server and that immediately shows onto the UWP application.
If the internet is out, the WebViews should still rotate between all the urls after the interval that is why we are using multiple webviews.
Currently, the situation is, that the more URLs I change, the more RAM it takes and eventually hangs.
WebViews is heavyweight control, please don't create multiple instance to render html page, for the scenario, you could use timer to change one webview's source with mvvm pattern for each 15s. Even if the internet is out, it will still work. Please check the following code.
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
initUri();
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(15);
timer.Tick += Timer_Tick;
timer.Start();
Source = new Uri("xxxxxx");
}
private List<Uri> _uris = new List<Uri>();
private void initUri()
{
_uris.Add(new Uri("xxxxxx"));
_uris.Add(new Uri("xxxxxx"));
_uris.Add(new Uri("xxxxxx"));
}
int count = 0;
private void Timer_Tick(object sender, object e)
{
Source = _uris[count];
count++;
if (count == _uris.Count)
{
count = 0;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Uri _source;
public Uri Source
{
get
{
return _source;
}
set
{
_source = value;
OnPropertyChanged();
}
}
}
Xaml
<WebView
x:Name="MyWebView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{x:Bind Source, Mode=OneWay}"
/>

Label Text not Updating from ViewModel Xamarin Forms

I am trying to get the text of a label to update on the front of end of my app.
At the moment Im using Message Centre to send a notification up to the view model and increment a number that should update on the label in the view.
Im using Xamarin Forms and PCL.
I can get the number to log out in the debug so I know the message centre is working. But its not updating the view.
the relevant Xaml:
<Label Text="{Binding counter}"
Grid.Row="0"/>
The code behind:
public partial class DriverDashboardView : ContentPage
{
private DriverDashboardViewModel driverdashboardviewmodel;
public DriverDashboardView()
{
InitializeComponent();
this.Title = "Driver's Dashboard";
BindingContext = driverdashboardviewmodel = new DriverDashboardViewModel();
dataList.ItemTapped += DataList_ItemTapped;
}
private void DataList_ItemTapped(object sender, ItemTappedEventArgs e)
{
DisplayAlert("Route Information","Various Data","OK");
}
protected async override void OnAppearing()
{
base.OnAppearing();
await driverdashboardviewmodel.GetLabelInfo();
}
}
The View Model:
public class DriverDashboardViewModel:BaseViewModel,INotifyPropertyChanged
{
private int messageCounter { get; set; }
public string counter { get { return messageCounter.ToString(); }
set {
if (Equals(value, messageCounter)) return;
messageCounter = Convert.ToInt32(value);
OnPropertyChanged(nameof(counter));
} }
public DriverDashboardViewModel()
{
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
And the relevant section that implements the message centre:
Foregroundmessages.cs:
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, "Increase");
As stated the messaging centre works fine. It gets as far as the view model but doesnt update the counter variable to the view. I have tried setting the counter as an int and a string hence the conversion in the get and set.
I also tried observable collection but that seemed redundant because its a single variable not a collection or list.
Any ideas?
your code is updating the private messageCounter property, not the public counter property that you are binding to. Updating messageCounter does not cause PropertyChanged to fire.
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});

IBasicVideoEffect processes only form 3 to 8 first frames

I created a simple test UWP app with one MainPage, that has MediaPlayer:
public sealed partial class MainPage
{
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var mediaPlayer = new MediaPlayer
{
Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/preview.mp4")),
AutoPlay = true
};
mediaPlayer.AddVideoEffect(typeof(VideoEffect).FullName, true, null);
}
}
and WinRT component with IBasicVideoEffect inherited class that notifies me how many frames were processed:
public sealed class VideoEffect : IBasicVideoEffect
{
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties => new List<VideoEncodingProperties>();
public bool IsReadOnly => false;
public MediaMemoryTypes SupportedMemoryTypes => MediaMemoryTypes.Gpu;
public void SetProperties(IPropertySet configuration) { }
public bool TimeIndependent => false;
public void Close(MediaEffectClosedReason reason) { }
public void DiscardQueuedFrames() { }
private int _frameCounter;
public void ProcessFrame(ProcessVideoFrameContext context)
{
_frameCounter++;
Debug.WriteLine("Frame #" + _frameCounter);
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
Debug.WriteLine("SetEncodingProperties");
}
}
If I run it - only 3 frames will be processed no matter what video file will be.
If I set breakpoint where _frameCounter increments I'll manage to hit F5 for 8 frames.
Why and how can I get all the frames to be processed?
I can solve it using MediaClip and MediaComposition as many examples say, but in this case frames are processed by CPU not GPU video engine which is not my goal.
The problem here is that you are playing a MediaPlayer without displaying it in XAML. So only the first few frames was processed as the media is not rendering on UI, there is no need to process other frames.
To make your VideoEffect work, you can use MediaPlayerElement control with MediaPlayerElement.SetMediaPlayer method to to render the media.
XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<MediaPlayerElement x:Name="mediaPlayerElement" />
</Grid>
Code-behind:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var mediaPlayer = new MediaPlayer
{
Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/preview.mp4")),
AutoPlay = true
};
mediaPlayer.AddVideoEffect(typeof(VideoEffect).FullName, true, null);
mediaPlayerElement.SetMediaPlayer(mediaPlayer);
}
After this, you will see _frameCounter increases while the media is playing.
with MediaPlayer you have to add effect before set source.
effect.AddVideoEffect(_player);
_player.Source = MediaSource.CreateFromMediaStreamSource(source);

Update the MessageBoxText runtime using System.Windows.MessageBox?

I'm using System.Windows.MessageBox to show the message to user, But I want to update the text on that Messagebox, after showing it.
The example in my case, I want to show the MessageBox with the content that can change at runtime as below:
"The system will be restarted in 5 seconds"
"The system will be restarted in 4 seconds"
"The system will be restarted in 3 seconds"
"The system will be restarted in 2 seconds"
"The system will be restarted in 1 second"
"The system will be restarted in 0 second"
Someone can show me how to do it?
Many thanks,
T&T
I think it's easier to use another window instead of MessageBox. Then you turn off features that you don't want (resizing, close button), make it modal, set up timer event handling, and so on.
Someone can show me how to do it
You can't do it with standard messagebox i.e. System.Windows.MessageBox.
Alternative:
Though what you can do is to define a custom message box(a windows form) with a label on it that you update via event asynchronously. And you use that to display user the count down.
This can be possible with MessageBox from Extended WPF Toolkit.
It has Text dependency property, which can be data bound, but, unfortunately, MessageBox initialization is hidden, and solution contains more than single line:
First of all, we need our MessageBox ancestor, because we're going to call protected InitializeMessageBox method (to apply standard message box settings). Then, we need to make our Show overload, which will apply binding to Text:
class MyMessageBox : Xceed.Wpf.Toolkit.MessageBox
{
public static MessageBoxResult Show(object dataContext)
{
var messageBox = new MyMessageBox();
messageBox.InitializeMessageBox(null, null, "Hello", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel);
messageBox.SetBinding(MyMessageBox.TextProperty, new Binding
{
Path = new PropertyPath("Text"),
Source = dataContext
});
messageBox.ShowDialog();
return messageBox.MessageBoxResult;
}
}
Next, we need a data context:
sealed class MyDataContext : INotifyPropertyChanged
{
public string Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private string text;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And a code, which will change the text in message box:
public partial class MainWindow : Window
{
private readonly MyDataContext dataContext;
private readonly DispatcherTimer timer;
private int secondsElapsed;
public MainWindow()
{
InitializeComponent();
dataContext = new MyDataContext();
timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.ApplicationIdle,
TimerElapsed, Dispatcher.CurrentDispatcher);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
secondsElapsed = 0;
timer.Start();
MyMessageBox.Show(dataContext);
timer.Stop();
}
private void TimerElapsed(object sender, EventArgs e)
{
dataContext.Text = string.Format("Elapsed {0} seconds.", secondsElapsed++);
}
}
The benefit of this approach is that you don't need to write yet another message box.

looping through a folder of images in c# / WPF

So i'm trying to loop through a folder and change the image source each 2 seconds.
I think my code is right, but I seem to be missing something since my image won't update, but I don't get an error.
The code populates my array of files so it finds the pictures, I'm just doing something wrong to set the image source.
XAML code
<Grid>
<Image x:Name="Picture" Source="{Binding ImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
C# code
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource {
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Start();
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
_MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
Anyone know what I'm doing wrong ?
Updated code
XAML
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
C#
public partial class IntroScreen : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource");
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
counter = 0;
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Enabled = true;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
I'm not getting any error's but the image still isen't switching. I'm wondering if my paths are even working. Is there any way to test this ?
You have forgot to do notify the update to MainImageSource to the binding.
To do so, you have to implement the interface : INotifyPropertyChanged and define DataContext.
And, as written in the MSDN documentation "Setting Enabled to true is the same as calling Start, while setting Enabled to false is the same as calling Stop.".
Like this:
public partial class IntroScreen : Window, INotifyPropertyChanged
{
private string[] files;
private Timer timer;
private int counter;
private int Imagecounter;
BitmapImage _MainImageSource = null;
public BitmapImage MainImageSource // Using Uri in the binding was no possible because the Source property of an Image is of type ImageSource. (Yes it is possible to write directly the path in the XAML to define the source, but it is a feature of XAML (called a TypeConverter), not WPF)
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource"); // Don't forget this line to notify WPF the value has changed.
}
}
public IntroScreen()
{
InitializeComponent();
DataContext = this; // The DataContext allow WPF to know the initial object the binding is applied on. Here, in the Binding, you have written "Path=MainImageSource", OK, the "MainImageSource" of which object? Of the object defined by the DataContext.
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new Timer();
timer.Elapsed += timer_Tick;
timer.Interval = 2000;
// Initialize "files", "Imagecounter", "counter" before starting the timer because the timer is not working in the same thread and it accesses these fields.
files = Directory.GetFiles(#"../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
timer.Start(); // timer.Start() and timer.Enabled are equivalent, only one is necessary
}
private void timer_Tick(object sender, EventArgs e)
{
// WPF requires all the function that modify (or even read sometimes) the visual interface to be called in a WPF dedicated thread.
// IntroScreen() and MainWindow_Loaded(...) are executed by this thread
// But, as I have said before, the Tick event of the Timer is called in another thread (a thread from the thread pool), then you can't directly modify the MainImageSource in this thread
// Why? Because a modification of its value calls OnPropertyChanged that raise the event PropertyChanged that will try to update the Binding (that is directly linked with WPF)
Dispatcher.Invoke(new Action(() => // Call a special portion of your code from the WPF thread (called dispatcher)
{
// Now that I have changed the type of MainImageSource, we have to load the bitmap ourselves.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(files[counter], UriKind.Relative);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // Don't know why. Found here (http://stackoverflow.com/questions/569561/dynamic-loading-of-images-in-wpf)
bitmapImage.EndInit();
MainImageSource = bitmapImage; // Set the property (because if you set the field "_MainImageSource", there will be no call to OnPropertyChanged("MainImageSource"), then, no update of the binding.
}));
if (++counter == Imagecounter)
counter = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And your XAML does not refer to the correct property:
<Grid>
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
Why do you need to implement INotifyPropertyChanged?
Basically, when you define a binding, WPF will check if the class that contains the corresponding property defines INotifyPropertyChanged. If so, it will subscribe to the event PropertyChanged of the class.
I'm not seeing any use of the INotifyPropertyChanged interface, which would be required to update a UI item the way you are using it. As it is now, the UI control has no way of knowing that the value was updated.

Categories