Odd File Association Handling Bug - c#

I have this in App.xaml.cs:
protected override void OnFileActivated(FileActivatedEventArgs args)
{
Window.Current.Content = new Frame();
((Frame)Window.Current.Content).Navigate(typeof(MainPage), args);
Window.Current.Activate();
}
This in MainPage.xaml.cs:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
FileActivatedEventArgs filesArgs = (FileActivatedEventArgs)e.Parameter;
StorageFile file = (StorageFile)filesArgs.Files[0];
mc.SetSource(await file.OpenReadAsync(), file.ContentType);
mc.Play();
}
And this in MainPage.xaml:
<MediaElement x:Name="mc" />
Now, I am facing a very odd problem. I have associated my app with .MP4 files. Whenever, I open any file, it doesn't get played immediately. For eg.
I open a.mp4, it doesn't get played and I don't close the app.
I open b.mp4, it doesn't get played and I don't close the app.
Then, I open a.mp4, it gets played. If it doesn't, I try again and it gets played. Now if I open any MP4 file, it gets played without any problems until I close the app.
So, this workaround works sometimes:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
FileActivatedEventArgs filesArgs = (FileActivatedEventArgs)e.Parameter;
StorageFile file = (StorageFile)filesArgs.Files[0];
StorageFile file2 = (StorageFile)filesArgs.Files[0];
mc.SetSource(await file2.OpenReadAsync(), file2.ContentType);
mc.SetSource(await file2.OpenReadAsync(), file2.ContentType);
mc.Play();
}
Does anyone know why it is not working without the workaround?

It looks as if the file doesn't play if you set the source and start playing to early, before the control is intialized and/or completely loaded. That's the reason why it works on subsequent calls when the app is already loaded and occasionally even on the first call. I made a simple app and managed to reproduce your problem most of the tries (although sometimes it worked).
I tried a simple workaround to always wait for the MediaElement to get loaded before I started playing and it seems that the problem is gone - I couldn't reproduce it in more than a dozen calls.
Here's what I've done:
MainPage.xaml:
<MediaElement x:Name="mc" Loaded="mc_Loaded" />
MainPage.xaml.cs
bool loaded = false;
Task task = new Task(() => {});
private void mc_Loaded(object sender, RoutedEventArgs e)
{
loaded = true;
task.Start();
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
FileActivatedEventArgs filesArgs = (FileActivatedEventArgs)e.Parameter;
StorageFile file = (StorageFile)filesArgs.Files[0];
if (!loaded)
await task;
mc.SetSource(await file.OpenReadAsync(), file.ContentType);
mc.Play();
}
I don't really like my solution because it's only based on guessing and empirical testing, but I couldn't find any documentation stating what needs to happen to MediaElement before it's ready.

Related

Memory Leak playing videos on Windows IoT | UWP

I've built an app that can read video files from an USB drive and switch between them using physical buttons. The app works well for a while, but after a while the device (DragonBoard 410c, latest Windows Insider Preview Build 15051) crashes due to the fact that all memory has been consumed by the app.
Looking at the processes in the device portal, I can see the "Working Set" memory jump each time I switch a video file while the "Private Working Set" roughly stays the same (around 30MB).
Here's how I load the video file:
C#
private IReadOnlyList<StorageFile> _videofiles
// list all available video files
public void Init(){
var queryOptions = new QueryOptions();
queryOptions.FolderDepth = depth;
foreach (var fileType in fileTypes)
{
queryOptions.FileTypeFilter.Add(fileType);
}
var query = KnownFolders.RemovableDevices.CreateFileQueryWithOptions(queryOptions);
_videofiles = await query.GetFilesAsync();
}
private async void SelectVideo(int videoId)
{
StorageFile videofile = _videofiles.Where(x => x.DisplayName == videoId.ToString()).FirstOrDefault();
if (videofile != null)
{
Debug.WriteLine($"Video {videofile.DisplayName} was selected");
var stream = await videofile.OpenAsync(FileAccessMode.Read);
VideoPlayer.SetSource(stream, videofile.FileType);
}
}
// since the button interrupt is not on the UI thread, SelectVideo() is called like this
private async void SelectVideoMarshalled(int videoId)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
SelectVideo(videoId);
});
}
XAML
<ContentControl x:Name="VideoPlayer" Content="{x:Bind ViewModel.VideoPlayer, Mode=OneWay}"/>
I have tried running GC.Collect() manually in several places, but no luck yet. Any ideas?
Since you have a StorageFile object, I recommend using the Source property and the file's Path instead of SetSource and opening the Stream manually.
Additionally, you should always null out the MediaElement when you're done with it (best done in OnNavigatingFrom).
Here's your code, simplified:
private void SelectVideo(string videoId)
{
var videofile = _videofiles.FirstOrDefault(x => x.DisplayName == videoId.ToString());
if (videofile == null) return;
Debug.WriteLine($"Video {videofile.DisplayName} was selected");
VideoPlayer.Source = new Uri(videofile.Path);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
VideoPlayer.Stop();
VideoPlayer.Source = null;
base.OnNavigatedFrom(e);
}
I also have a side comment, you can x:Bind event handlers to the ViewModel.
For example, if your video file list is a ListView of string:
public void VideosListView_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e?.AddedItems?.Count > 0)
{
var fileDisplayName = e.AddedItems.FirstOrDefault() as string;
if (!string.IsNullOrEmpty(fileDisplayName))
SelectVideo(fileDisplayName);
}
}
Notice I only need to change the method signature to public and then in the XAML you can do this:
<ListView ItemsSource="{x:Bind ViewModel.VideoFiles, Mode=OneTime}"
SelectionChanged="{x:Bind ViewModel.VideosListView_OnSelectionChanged}"/>
No need to marshal back to the UI thread :)
Lastly, you can check out the demo here on GitHub where I've implemented something similar to this.
Turns out my code was fine after all. I had a Windows Update stuck / failing several times which I didn't notice.
When the update finally completed successfully the memory leaks were gone.

Windows Phone 8.1 Share Contract

I wrote a Windows Phone 8.1 (WINRT) App. I am trying to share an image from my app which is in LocalStorage of the app. I am using Windows Phone 8.1 Share Contract.
private async void OnShareDataRequested(DataTransferManager sender, DataRequestedEventArgs _dataRequestedEventArgs)
{
_dataRequestedEventArgs.Request.GetDeferral();
List<StorageFile> ListObject = new List<StorageFile>();
Uri UriObject = new Uri(FileLocation,UriKind.RelativeOrAbsolute);
_dataRequestedEventArgs.Request.Data.Properties.Title = "Dr. App";
_dataRequestedEventArgs.Request.Data.Properties.Description = "Photo from my Dr. App Album.";
StorageFolder StorageFolderObject;
StorageFile StorageFileObject;
try
{
StorageFolderObject = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFolderAsync(LocalCache);
StorageFileObject = await StorageFolderObject.GetFileAsync(FileNameSaved);
_dataRequestedEventArgs.Request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromFile(StorageFileObject);
_dataRequestedEventArgs.Request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(StorageFileObject));
ListObject.Add(StorageFileObject);
_dataRequestedEventArgs.Request.Data.SetStorageItems(ListObject);
}
catch(Exception ex_)
{
}
finally
{
_dataRequestedEventArgs.Request.GetDeferral().Complete();
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested -= OnShareDataRequested;
base.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += OnShareDataRequested;
base.OnNavigatedTo(e);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.ShowShareUI();
}
I am getting PREPARING CONTENT TO SHARE and then it vanishes in a second. ShareUI doesnt open.
The documentation states that the asynchronous work has an upper limit of 200 ms. Are you violating this premise?
DataRequest.GetDeferral(): Use this method when you want to use an asynchronous function call to generate the DataPackage during a share operation. This function must return a DataPackage object within 200ms to prevent the operation from timing out. If your app shares content that takes more time to package, such as a collection of files or photos, don't use this method. Instead, use the SetDataProvider method to assign a delegate to a DataPackage and return that DataPackage to the target app.
Another thing that stands out to me when I look at your code is that you invoke the GetDeferral method twice instead of saving the result from the first invocation.

Save data on exit Windows Phone 8.1 (Universal Apps)

I have a question regarding saving/loading data in Windows Universal Apps 8.1.
I cannot seem to save the data when exiting the app. My call to the serialization is done in the OnSuspending method in App.xaml.cs, but I don't think it is called whenever I close the app.
It sometimes saves the data, sometimes not. Here is my code for OnSuspending and Save methods.
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
// TODO: Save application state and stop any background activity
Debug.WriteLine("SUSPENDING");
HabitManager.HabitSerializer.Save();
deferral.Complete();
}
public async static void Save()
{
Debug.WriteLine("SAVED");
var json = JsonConvert.SerializeObject(HabitList.Instance.GetHabits());
var habits = HabitList.Instance.GetHabits();
foreach (var h in habits)
{
Debug.WriteLine("S: " + h);
}
StorageFile saveFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
await Windows.Storage.FileIO.WriteTextAsync(saveFile, json);
}
So my question is, what could be the problem, and if this isn't a good serializing mechanism, can you suggest me a better one? Thank you
Your OnSuspending call returns before the Save call finishes.
Change Save to return a Task so you can await it:
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
// TODO: Save application state and stop any background activity
Debug.WriteLine("SUSPENDING");
// Wait fir Save to finish its a sync operations
await HabitManager.HabitSerializer.Save();
deferral.Complete();
}
public async static Task Save()
{
// Same save code
}
Also be careful when testing suspension. The app won't suspend normally while debugging, but VS provides a suspend button to simulate the suspension process.

Windows Phone, pick file using PickSingleFileAndContinue or PickMultipleFilesAndContinue

I got stuck trying to implementing file picker for windows phone app. I need to choose files from gallery using FileOpenPicker. I didn't get how it works. Here is my code:
private readonly FileOpenPicker photoPicker = new FileOpenPicker();
// This is a constructor
public MainPage()
{
// < ... >
photoPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
photoPicker.FileTypeFilter.Add(".jpg");
}
// I have button on the UI. On click, app shows picker where I can choose a file
private void bChoosePhoto_OnClick(object sender, RoutedEventArgs e)
{
photoPicker.PickMultipleFilesAndContinue();
}
So, what to do next? I guess I need to get a file object or something.
I found this link. It is msdn explanation where custom class ContinuationManager is implemented. This solution looks weird and ugly. I am not sure if it is the best one. Please help!
PickAndContinue is the only method that would work on Windows Phone 8.1. It's not so weird and ugly, here goes a simple example without ContinuationManager:
Let's assume that you want to pick a .jpg file, you use FileOpenPicker:
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".jpg");
picker.ContinuationData.Add("keyParameter", "Parameter"); // some data which you can pass
picker.PickSingleFileAndContinue();
Once you run PickSingleFileAndContinue();, your app is deactivated. When you finish picking a file, then OnActivated event is fired, where you can read the file(s) you have picked:
protected async override void OnActivated(IActivatedEventArgs args)
{
var continuationEventArgs = args as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
switch (continuationEventArgs.Kind)
{
case ActivationKind.PickFileContinuation:
FileOpenPickerContinuationEventArgs arguments = continuationEventArgs as FileOpenPickerContinuationEventArgs;
string passedData = (string)arguments.ContinuationData["keyParameter"];
StorageFile file = arguments.Files.FirstOrDefault(); // your picked file
// do what you want
break;
// rest of the code - other continuation, window activation etc.
Note that when you run file picker, your app is deactivated and in some rare situations it can be terminated by OS (little resources for example).
The ContinuationManager is only a helper that should help to make some things easier. Of course, you can implement your own behaviour for simpler cases.

MediaElement Source cannot be set after SaveState/LoadState

(Note: All code has been severely simplified.)
Problem
MediaElement source not being set after Suspend/Resume. The CurrentState quickly changes to "Closed" after the source is set.
I am handling the MediaFailed event — it doesn't fire. I am also handling the MediaOpened event, which doesn't fire either.
Details
I have the following method which updates the MediaElement's Source. It works really well as long as the app is not trying to resume after having been Suspended.
private async void UpdateMediaElementSource(object sender, EventArgs e)
{
var videoSource = this.DefaultViewModel.CurrentSource; // a string
var file = await StorageFile.GetFileFromPathAsync(videoSource);
var videoStream = await file.OpenAsync(FileAccessMode.Read);
this.videoMediaElement.SetSource(videoStream, file.ContentType);
// The above line works many times as long as the app is not trying to Resume.
}
When the app is Suspended it calls the SaveState method:
protected async override void SaveState(Dictionary<String, Object> pageState)
{
pageState["MediaElementSource"] = this.DefaultViewModel.CurrentSource;
// I also made the videoStream global so I can dispose it — but no dice.
this.videoStream.Dispose();
this.videoStream = null;
}
When the app Resumes, it calls the LoadState method:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
string source = string.Empty;
if (pageState != null)
{
if (pageState.ContainsKey("MediaElementSource"))
{
source = (string)pageState["MediaElementSource"];
}
}
var document = PublicationService.GetDocument(this.currentDocumentIdNumber);
this.DefaultViewModel = new DocumentViewModel(document);
this.DefaultViewModel.CurrentMarkerSourceChanged += UpdateMediaElementSource;
if (!string.IsNullOrEmpty(source))
{
// This causes the UpdateMediaElementSource() method to run.
this.DefaultViewModel.CurrentSource = source;
}
}
I appreciate any help on this issue. Please let me know if you need more details.
So, it turns out that the mediaElement's Source was being set before it was added to the visual tree.
Usually, this is not an issue when doing this:
mediaElement.Source = whatever;
but it IS an issue when you do this:
mediaElement.SetSource(stream, MimeType);
Conclusion
Make sure that your MediaElement is part of the VisualTree when you call SetSource(...).
A simple way to get my above code to work is by adding a global bool that is set to true once the mediaElement.Loaded event has fired. Then, inside the code that calls SetSource(), wrap that in an if(_mediaElementLoaded) block.

Categories