When you close the app by swiping it in recent apps, it will cancel any services and terminate most aspects of the app gracefully. However, if there are any notifications that were SetOngoing(true), then these will remain if the app suddenly is closed, and there aren't any services that listen for the app's termination.
What is the right way to deal with this problem?
Recently, I coded a music player, and I arranged it such that in the OnStop for my activities, the notification is canceled (and so is the thread updating the progress bar within it). Then, OnResume, I trigger the notification again.
If they "recent apps swipe" it away, or click away, the notification goes away now, as long as the music isn't playing. So to get rid of the notification, you have to pause it, and then swipe away. Otherwise, there is a leak memory if the app is closed by swipe, where the notification remains open and is buggy afterwards if the app is reopened, and the app crashes if you click the notification (though maybe that's because I can't figure out how to get started with saved state bundles). Likewise, there is a problem if you let the app close the notification every OnStop, as then it will be closed as the user does other things with their phone, even though the music is playing (which sort of defeats the point of it right?)
Are there other better ways to handle this? Who has a good saved state bundle if that is indeed relevant to my issue?
Thanks for the discussion
You can cancel the notification when android App is closed by swipe with the following code:
[Service]
public class ForegroundServiceDemo : Service
{
public override void OnTaskRemoved(Intent rootIntent)
{
//this.StopSelf();
//this.StopForeground(StopForegroundFlags.Remove);
this.StopService(new Intent(this,typeof(ForegroundServiceDemo)));
base.OnTaskRemoved(rootIntent);
}
}
By overriding the OnTaskRemoved method of the service, the system will call this method when user closes the app by swipe. And each of the three lines code can cancel the notification and stop the service when the app is closed by swipe.
I found this, finally, after trying every search terms imaginable, and wow there is a whole section on this. I do not have it working yet, but I can report back with code when I do. Here is the solution: https://developer.android.com/guide/topics/media-apps/media-apps-overview
Seems you have to implement the media player service as a specific kind of service that registers to the notification. I am in the process of refactoring the heart of my code, which perhaps should be terrifying, but feels more like the final algorithm on a Rubix's cube... I will report back in like 10 work hours with some working code (I hope).
Thanks to everyone contributing on this discussion!
OK, so, after much dabbling and dozens of work hours... I have found the best way to handle this issue is to create a MediaBrowserService with a MediaSession. In the notification creation code, it is very particular about how you start that notification (which has to be in the Foreground and bound to the MediaSession). Once this is done, the notification will stay open, even if you close the app, and clicking it will always bring you back to the activity bound to the service (see the supplied code below). Then, you just have a button on the notification to close itself and the app. Voila, a notification that does NOT remain open if the app is closed from the recent apps, etc.
public static void CancelNotificationBreadCrumb()
{
if (cts != null)
{
cts.Cancel();
Thread.Sleep(250);
// Cancellation should have happened, so call Dispose
cts.Dispose();
MyLogger.Debug("MyMediaPlayer: CloseEntireApp: Notification should have been disposed.");
}
}
public static void NotificationNowPlayingBreadCrumb()
{
try
{
Intent intent = MenuManager.GetGoToNowPlayingIntent(context, GetCurrentlyPlaying());
manager = (NotificationManager)context.GetSystemService(NotificationService);
PendingIntent pendingIntent = PendingIntent.GetActivity(context, 1, intent, PendingIntentFlags.Immutable);
NotificationChannel notificationChannel = new NotificationChannel(ChannelId, ChannelId, NotificationImportance.Low);
notificationChannel.EnableLights(false);
notificationChannel.EnableVibration(false);
notificationChannel.SetSound(null, null);
//notificationChannel.SetVibrationPattern(new long[] { 10, 20 });
manager.CreateNotificationChannel(notificationChannel);
Notification notification = NowPlayingAdapter.InflateNotification(context, currentFile, ChannelId, pendingIntent);
service.StartForeground(MY_MEDIA_NOTIFICATION_ID, notification);
manager.Notify(MY_MEDIA_NOTIFICATION_ID, notification);
// Then trigger the thread to update the real-time features
if (cts == null || cts.IsCancellationRequested)
cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(new WaitCallback(RunInBackground), cts.Token);
} catch(Exception e)
{
string message = "MyMediaPlayer: NotificationNowPlayingBreadCrumb: Could not create now playing breadcrumb notification; message: " + e.Message;
MyLogger.Error(message);
}
}
public static void CloseEntireApp()
{
MyLogger.Trace("MyMediaPlayer: Entering CloseEntireApp...");
if (player != null)
player.Release();
CancelNotificationBreadCrumb();
MediaReceiver.Dispose();
MediaSession.Dispose();
MyLogger.Trace("MyMediaPlayer: CloseEntireApp is Killing App. Good bye!");
service.StopSelf();
Android.OS.Process.KillProcess(Android.OS.Process.MyPid());
}
Here is the OnCreate method for my service:
public class MyMediaPlayer : MediaBrowserServiceCompat
{
private static MediaPlayer? player;
private static MusicAppFile? currentFile;
private static List<MusicAppFile>? allFilesInCurrentContext;
private static Context? context;
private static List<int> recentIndexes = new List<int>();
private static int maxRecentIndexes = 30;
private static bool shuffleMode = false;
private static ViewGroup? Parent;
private static NotificationManager? manager;
private static CancellationTokenSource? cts;
public static MediaButtonReceiver? MediaReceiver;
public static MediaSessionCompat? MediaSession;
private static PlaybackStateCompat.Builder stateBuilder;
private static MediaBrowserServiceCompat service;
public IBinder Binder { get; private set; }
public const string ActionPlay = "com.xamarin.action.PLAY";
public const string ActionPause = "com.xamarin.action.PAUSE";
public const string ActionNext = "com.xamarin.action.NEXT";
public const string ActionStop = "com.xamarin.action.STOP";
public const string ActionBack = "com.xamarin.action.BACK";
public const string ActionCloseApp = "com.xamarin.action.CLOSEAPP";
public static string ChannelId = "NowPlayingNote";
public static string MY_MEDIA_ROOT_ID = "media_root_id";
public static int MY_MEDIA_NOTIFICATION_ID = 1111111;
public static string MY_MEDIA_TAG = "media_tag";
public override void OnCreate()
{
base.OnCreate();
// Create a MediaSessionCompat
MediaSession = new MediaSessionCompat(context, MY_MEDIA_TAG);
// Enable callbacks from MediaButtons and TransportControls
MediaSession.SetFlags(
MediaSessionCompat.FlagHandlesMediaButtons |
MediaSessionCompat.FlagHandlesTransportControls);
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
stateBuilder = new PlaybackStateCompat.Builder()
.SetActions(
PlaybackStateCompat.ActionPlay |
PlaybackStateCompat.ActionPlayPause |
PlaybackStateCompat.ActionSkipToNext |
PlaybackStateCompat.ActionSkipToPrevious |
PlaybackStateCompat.ActionStop);
MediaSession.SetPlaybackState(stateBuilder.Build());
// MySessionCallback() don't do this. C# isn't as good at doing callbacks because you can't define them inline
// MediaSession.SetCallback(new MediaSessionCallback(this));
service = this;
// Set the session's token so that client activities can communicate with it.
SessionToken = MediaSession.SessionToken;
}
...
I create this service when they click to select a file in one of the menu activities (so in a method called by a method called by an OnClick delegate):
if (musicMenu != null)
{
bool stillPlayingSameFile = MyMediaPlayer.UpdateCurrentContext(c, musicMenu, mf);
if (cts == null)
{
// Start the service and tell it to call play
InitiateMediaBrowserService(c);
} else
{
MyMediaPlayer.Play(stillPlayingSameFile);
}
}
GoToNowPlaying(c, mf);
and the inner service there:
public static void InitiateMediaBrowserService(Context c)
{
// Start the service and tell it to call play
Intent intent = new Intent(c, typeof(MyMediaPlayer));
intent.SetAction(MyMediaPlayer.ActionPlay);
cts = new CancellationTokenSource();
Platform.AppContext.StartForegroundService(intent);
}
Ok, so now the play service, which is triggered from the action play intent here, and makes the call to start the notification, which is where the StartForeground call is made (see the first snippet at the top):
public static void Play(bool stillPlayingSameFile)
{
// If the player has not been created before, or it is a new track, then it needs to be recreated
if (player == null || !stillPlayingSameFile)
{
// If we're here to recreate the player, destroy the old one in memory first
if (player != null)
player.Release();
// Then add the new player
if (currentFile != null)
{
Uri uri = Android.Net.Uri.Parse(currentFile.FilePath);
MediaPlayer media = MediaPlayer.Create(context, uri);
media.Completion += OnCompletion;
if (MediaReceiver == null)
MediaReceiver = new MediaButtonReceiver(context);
media.RoutingChanged += MediaReceiver.OnRoutingChanged;
player = media;
player.SetWakeMode(context, WakeLockFlags.Partial);
}
// Finally, add this file to the list of those recently played
int indexToPlay = allFilesInCurrentContext.IndexOf(currentFile);
if (indexToPlay >= 0)
recentIndexes.Add(indexToPlay);
if (recentIndexes.Count > maxRecentIndexes)
recentIndexes.RemoveAt(0);
}
// Finally start the player, which picks up where left off if this is the same track
if (!IsPlaying() || !stillPlayingSameFile)
{
player.Start();
NotificationNowPlayingBreadCrumb();
}
}
The MediaButtonReceiver and MediaBroadcastReceiver classes are pretty straightforward, so comment if you really need that code. One other thing to note is that you do have to bind the service to an activity (I suggest the now playing activity):
protected override void OnStart()
{
base.OnStart();
//Config.ConfigureBluetoothIntegration(this); TODO remove this
Intent serviceToStart = new Intent(this, typeof(MyMediaPlayer));
//serviceToStart.SetAction(MyMediaPlayer.ActionPlay);
BindService(serviceToStart, new ServiceConnection(this), Bind.AutoCreate);
}
So there, now there IS an example of how to use the MediaSession and MediaSessionCompat and MediaBrowserServiceCompat online somewhere. Even ChatGPT could not find an example or tell me how to do this. You are welcome, internet. Enjoy your coding!
Related
I'm using push notification with FCM in a Xamarin Forms application, so everything looks to be working fine except for one specific case.
Used package: Plugin.FirebasePushNotification
The event CrossFirebasePushNotification.Current.OnNotificationReceived += is being called only once when the application is open and when the app starts.
if I send 2 or more notifications from the server it's only called for the first notification, after that stop working. However the notification popup is always shown no matter what, even when the is in foreground, background, killed.
I want this to be called when the app is open because I need to perform an action depending on the notification data.
I'm testing in iOS 15.3.1
Intructions: https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/GettingStarted.md
Versions:
"Plugin.FirebasePushNotification" Version="3.4.1"
"Xamarin.Forms" Version="5.0.0.2337"
Thanks in advance.
My entire AppDelegate.cs Code:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
NSObject _onKeyboardShowObserver;
NSObject _onKeyboardHideObserver;
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
Xamarin.FormsMaps.Init();
Firebase.Core.App.Configure();
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
RegisterKeyBoardObserver();
FirebasePushNotificationManager.Initialize(options, true);
return base.FinishedLaunching(app, options);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
FirebasePushNotificationManager.DidRegisterRemoteNotifications(deviceToken);
}
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
FirebasePushNotificationManager.RemoteNotificationRegistrationFailed(error);
}
// To receive notifications in foreground on iOS 9 and below.
// To receive notifications in background in any iOS version
//[Export("application:didReceiveRemoteNotification")]
[Export("messaging:didReceiveRemoteNotification:withCompletionHandler:")]
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired 'till the user taps on the notification launching the application.
// If you disable method swizzling, you'll need to call this method.
// This lets FCM track message delivery and analytics, which is performed
// automatically with method swizzling enabled.
FirebasePushNotificationManager.DidReceiveMessage(userInfo);
// Do your magic to handle the notification data
System.Console.WriteLine(userInfo);
completionHandler(UIBackgroundFetchResult.NewData);
}
void RegisterKeyBoardObserver()
{
if (_onKeyboardShowObserver == null)
_onKeyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow((object sender, UIKeyboardEventArgs args) =>
{
NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
CGSize keyboardSize = result.RectangleFValue.Size;
MessagingCenter.Send<object, KeyboardAppearEventArgs>(this, Constants.iOSKeyboardAppears, new KeyboardAppearEventArgs { KeyboardSize = (float)keyboardSize.Height });
});
if (_onKeyboardHideObserver == null)
_onKeyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide((object sender, UIKeyboardEventArgs args) =>
MessagingCenter.Send<object, string>(this, Constants.iOSKeyboardDisappears, Constants.iOSKeyboardDisappears));
}
public override void WillTerminate(UIApplication application)
{
if (_onKeyboardShowObserver == null)
{
_onKeyboardShowObserver.Dispose();
_onKeyboardShowObserver = null;
}
if (_onKeyboardHideObserver == null)
{
_onKeyboardHideObserver.Dispose();
_onKeyboardHideObserver = null;
}
}
}
Some suggestions for troubleshooting .
As Almis mentioned , place the code into OnStart of App.cs.
Make sure that initialization is correct, check iOS Initialization .
If problem persists , consider raising issue on github .
After different tests I could find what is causing the issue, but I don’t know why.
One of the things I do in the OnNotificationReceived event is show a local Push Notification by calling the NotificationCenter.Current.Show method, and that is what is causing the problem. When I comment the line of code that calls await NotificationCenter.Current.Show(notification) start working fine.
Once the NotificationCenter.Current.Show is executed, then the CrossFirebasePushNotification plugging starts working like if my App is in background or foreground.
This seems to be a tricky one:
We're working on a Xamarin iOS app that has a UITableView control displaying a list of profiles. Once the user clicks on one of the profiles we'd like to switch to a separate "ProfileViewController" to display more detailed information. This works just fine for 99.9% of the time. In the remaining ~0.1% the item in the list gets highlighted (which happens OnClick) but the app just seemingly freezes and never switches to the other ViewController, leaving the user with an unresponsive list.
The interesting thing to note here is that the app doesn't really freeze, as the simple "swipe back" gesture brings the user back to the UITableView (to be clear the switch-to-profile-viewcontroller animation is never played so it basically switches from the unresponsive list back to the same list, but now it is responsive again).
The tricky thing is that we can't reliably reproduce this bug. It just seems to happen at random, sometimes while stuff is running in the background, other times while the app was previously in an idle state. We are pretty sure that it isn't related to multithreading (we triple-checked everything, using locking and semaphores where necessary), it rather seems to be some rendering issue (as you can still swipe back to the previous screen or at least it isn't your common dead lock).
Using a bunch of Console.WriteLine() tracers and hours of trial and error reproducing this bug we could isolate the problem and discovered the following:
Upon clicking on the list code in ViewDidLoad() and ViewWillAppear() is successfully executed. However ViewDidAppear() never gets invoked. There is nothing else happening in our code or on different threads between ViewWillAppear() and ViewDidAppear() and we don't have any funky / unusual code that gets executed previously (just the usual UI initialization like this in ViewDidLoad()):
...
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
LblProfileRelationInfo.TextAlignment = UITextAlignment.Center;
LblProfileRelationInfo.Text = string.Empty;
LblProfileRelationInfo.Font = FontAgent.ForSize(15);
...
At this point we are kinda out of ideas as to what could be going wrong here and we have independently reviewed any of our code that could remotely be involved in this bug but we found nothing.
We didn't find anything related online but maybe someone else has encountered a similar issue like this in Xamarin / Xamarin iOS before?
Are there any steps we could take that we don't know of? When breaking the app in the Visual Studio for Mac debugger during the freezes the call stacks only contain native code and are not of much use.
Any help or ideas as to what else we could try are hugely appreciated :)
Edit: Adding some code
This is our BaseViewController defining some initialization methods to be used by it's children (again in 99.9% of all cases this works just fine):
public abstract class BaseViewController : UIViewController
{
public BaseViewController(string nibName, NSBundle nSBundle) : base(nibName, nSBundle)
{
ControllerDidInitialize();
}
public ClubmappViewController(IntPtr handle) : base(handle)
{
ControllerDidInitialize();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
CustomOrientationService.ScreenRotationPortrait();
InitializeStaticContent();
Console.WriteLine("ViewDidLoad() exits just fine...");
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("When the bug occurs this never gets executed :C");
RefreshUI();
OnViewDidAppear?.Invoke(this, new EventArgs());
LoadData();
}
private protected abstract void InitializeStaticContent();
private protected abstract void RefreshUI();
private protected virtual void LoadData()
{
}
private protected virtual void ControllerDidInitialize()
{
}
}
This is the ProfileViewController that inherits from BaseViewController and that just stops being rendered or inititialized or whatever the actual problem might be (it just never shows up as described above) but most of the time it works just fine:
public partial class ProfileViewController : BaseViewController
{
public const string STORYBOARD_ID = "ProfileViewController";
public ProfileViewController(IntPtr handle) : base(handle)
{
}
private protected override void ControllerDidInitialize()
{
// do initialization things like initializing variables, etc
// no real logic here
}
private protected override void InitializeStaticContent()
{
// layouting loading
LblProfileTitle.TextAlignment = UITextAlignment.Center;
LblProfileTitle.Text = string.Empty;
LblProfileTitle.Font = FontAgent.ForSize(20);
LblProfileTitle.Font = UIFont.BoldSystemFontOfSize(20);
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
// ... and so on ..
}
private protected override void RefreshUI()
{
// ... theme related stuff ...
ViewProfileActive.BackgroundColor = ThemeAgent.CurrentTheme.OnlineIndicatorColor;
LblProfileName.TextColor = ThemeAgent.CurrentTheme.PrimaryTextColor;
LblProfileSlogan.TextColor = ThemeAgent.CurrentTheme.SecondaryTextColor;
// ...
}
private protected async override void LoadData()
{
// load user data ...
ProfileData data = await ...
}
}
And this is the "OnClick" event that is triggered when a profile is clicked on, that's supposed to initialize and show the ProfileViewController (it's unlikely that something's wrong with this but including it nonetheless):
// ...
(sender, e) =>
{
ProfileViewController profileController = (ProfileViewController)UIStoryboard.FromName("Main", null).InstantiateViewController(ProfileViewController.STORYBOARD_ID);
profileController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
currentViewController.NavigationController.PushViewController(profileController, true);
}
// ...
I tried to do a video streaming application with Unity and I have successfully connect two user and are able to video chat & sharescreen. The problem is now when the other/second user press leave channel and rejoin again, first user will see the second user screen just stopped at the last frame. Only way to deal with it right now is to exit Unity play mode and re-enter again. Below are the code:
void LoadEngine()
{
print("Loading Engine...");
if (engine != null)
{
print("Engine already exists. Please unload it first!");
return;
}
engine = IRtcEngine.GetEngine(appId);
engine.SetLogFilter(LOG_FILTER.DEBUG);
}
void UnloadEngine()
{
print("Unloading Engine...");
if (engine != null)
{
IRtcEngine.Destroy();
engine = null;
}
}
void EnableVideo(bool yes)
{
engine.DisableAudio();
if (yes)
{
engine.EnableVideo();
engine.EnableVideoObserver();
}
else
{
engine.DisableVideo();
engine.DisableVideoObserver();
}
}
private void JoinChannel()
{
print("Joining Channel...");
EnableVideo(true);
// add our callback to handle Agora events
engine.OnJoinChannelSuccess += OnJoinChannelSuccess;
engine.OnUserJoined += OnUserJoined;
engine.OnUserOffline += OnUserLeave;
engine.OnLeaveChannel += OnLeaveChannel;
button_Join.onClick.RemoveListener(JoinChannel);
button_Join.onClick.AddListener(LeaveChannel);
button_Join.GetComponentInChildren<Text>().text = "Leave Channel";
if (string.IsNullOrEmpty(channelName))
{
return;
}
engine.JoinChannel(channelName, null, 0);
}
private void LeaveChannel()
{
print("Leaving Channel...");
button_Join.onClick.RemoveListener(LeaveChannel);
button_Join.onClick.AddListener(JoinChannel);
button_Join.GetComponentInChildren<Text>().text = "Join Channel";
playerOne.Clear();
playerTwo.Clear();
engine.LeaveChannel();
EnableVideo(false);
engine.OnJoinChannelSuccess -= OnJoinChannelSuccess;
engine.OnUserJoined -= OnUserJoined;
engine.OnUserOffline -= OnUserLeave;
engine.OnLeaveChannel -= OnLeaveChannel;
}
private void OnJoinChannelSuccess(string channelName, uint uid, int elapsed)
{
print("Joined with uid " + uid);
myId = uid;
playerOne.Set(0);
}
private void OnUserJoined(uint uid, int elapsed)
{
string userJoinedMessage = string.Format("onUserJoined callback uid {0} with elapsed {1}", uid, elapsed);
print(userJoinedMessage);
playerTwo.Set(uid);
}
private void OnUserLeave(uint uid, USER_OFFLINE_REASON reason)
{
string userLeaveMessage = string.Format("onUserOffline callback uid {0} with reason {1}", uid, reason);
print(userLeaveMessage);
playerTwo.Clear();
}
private void OnLeaveChannel(RtcStats stats)
{
playerTwo.Clear();
}
private void OnApplicationQuit()
{
UnloadEngine();
}
Is there anything I missed out in the code?
Edit
I suspect this is Agora API bugs where the video surface will not continue to stream the video when the UID was being changed in the runtime. This explained when the same user leave and rejoin, he will get different uid everytime he rejoin. The video surface will need to set UID to another number which causes the video surface stopped streaming.
Solution
I solved it using unity instantiate to real time instantiate the video surface when connected to channel and destroy the video surface game object when leaving a channel.
first of all, great name! ;)
I'm glad that you were able to work out a solution.
Another technique you can try, is calling:
yourVideoSurface.SetForUser(yourNewUID);
It looks like you're already implicitly doing that in your playerOne.Set(0) call, however I'd figure I'd mention it just in case.
I had the same issue in Unity. The tmp was returning -1. In the Update loop of VideoSurface.cs if this occurs, the update loop stops.
if (tmpi == -1)
return;
I agree with Jeremy, the Agora VideoSurface doesn't support changing users. I set the UID on the VideoSurface manually and it fixed the issue. I also agree that the sdk implies that it would change- which is misleading and frustrating.
I believe the issue may occur because VideoSurface.cs sets uint uid = mUid; in the update loop, so it is set to the default ID when the Unity scene starts then errors out when videoSurface.SetForUser(uid); is called by TestHelloUnityVideo when a user joins.
public class WatchFolder
{
public bool ChangesMade { get; set; }
public WatchFolder()
{
ChangesMade = false;
}
public async void InitializeFolderWatch()
{
var folder = KnownFolders.PicturesLibrary.GetFolderAsync("TestWatchFolder");
var options = new QueryOptions();
options.FolderDepth = FolderDepth.Deep;
var fileQuery = folder.CreateFileQueryWithOptions(options);
fileQuery.ContentsChanged += OnContentsChangedFolder;
var files = await fileQuery.GetFilesAsync();
}
private void OnContentsChangedFolder(IStorageQueryResultBase sender, object args)
{
ChangesMade = true;
}
}
I'm trying to get a watch folder working for a Windows 10 Store app. When debugging the above code works fine at first. The event fires and will continue to fire every time I add a file to the watch folder (or remove one) using Windows File Explorer. But if I interact with the uwp app's UI for a few seconds then then try again it won't fire thereafter.
I have tried instantiating it in both the code-behind and the view model, same result. I've also tried converting it to a static method, same result.
It is because the fileQuery object is going out of scope and when you are interacting with the UWP app, the GC is kicking in and removing the object from memory.
try to make the fileQuery object a private field in the class, this way it wont go out of scope until you don't need the class anymore.
I am making a UWP App where I run Background Audio in the MainPage on a Button Click event. When I move to another page, there's also a different Media to play in Background Audio Task there.
How can I stop the currently playing Task to run the other? Should I define something globally? Any help regarding this issue?
Edit
I am using this sample: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BackgroundAudio While the backgroundAudio of the first Page is running, I go to the second page and on a click event I set a new List with the following code:
// First update the persisted start track
ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.TrackId, RadioFacade.mySongs[0].MediaUri.ToString()); //here
ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.Position, new TimeSpan().ToString());
// Start task
StartBackgroundAudioTask();
But the new song takes more than the estimated time to run and enter the else of this method:
private void StartBackgroundAudioTask()
{
AddMediaPlayerEventHandlers();
var startResult = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
bool result = backgroundAudioTaskStarted.WaitOne(10000);
//Send message to initiate playback
if (result == true)
{
MessageService.SendMessageToBackground(new UpdatePlaylistMessage(RadioFacade.mySongs));
MessageService.SendMessageToBackground(new StartPlaybackMessage());
}
else
{
throw new Exception("Background Audio Task didn't start in expected time");
}
});
startResult.Completed = new AsyncActionCompletedHandler(BackgroundTaskInitializationCompleted);
}
and the old (first playing) song keeps playing.
I tried to Stop the current BackgroundMediaPlayer using BackgroundMediaPLayer.Shutdown() but it didn't work.
Any idea how to let the old song stop and the current song play?
You can control the background media player by sending messages to it from the foreground. For example,
From the foreground app:
BackgroundMediaPlayer.SendMessageToBackground(new ValueSet
{
{"playnew", "some value"}
});
In your background task:
public sealed class AudioPlayer : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
BackgroundMediaPlayer.MessageReceivedFromForeground += BackgroundMediaPlayer_MessageReceivedFromForeground;
...
...
}
private async void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e)
{
object value;
if (e.Data.TryGetValue("playnew", out value) && value != null)
{
// value will be whatever you pass from the foreground.
BackgroundMediaPlayer.Current.Pause();
BackgroundMediaPlayer.Current.Source = stream source;
BackgroundMediaPlayer.Current.Play();
}
}
...
}
"playnew" can be a global constant in your application. You can use the ValueSet to pass the live stream url to the background task as the value.