Tuesday, April 19, 2016

The program that went “ding”!

I started with a simple requirement: build a timer program that, after 25 minutes, does a little “ding” sound, using the Universal Windows platform.

Making a sound is simple

The “make a sound” is simple enough; the fundamental API is the MediaElement, and it has a Play method that takes a stream.  Universal is a little awkward with streams; the final code:
  • Get the Windows.ApplicationModel.Package
    .Current.InstalledLocation
  • from the InstalledLocation do a GetFileAsync passing in the normal  @“\Assets\Sounds\ding.wav” format.  Note my use of the @ in front of the quote.
  • given the file do an OpenAsync()
  • On the MediaElement do a SetSource() of the stream
  • Finally, you can Play().

Except when you don’t have focus

Unfortunately this runs into two problems, both discovered by an end user.  Firstly, it won’t ding when the app isn’t the foreground. Secondly, it doesn’t ding when the app is minimized.  You might think this is the same problem, but it’s not.
To work in the background you need to set the AudioCategory of the MediaElement to BackgroundCapableMedia.  This won’t work until you’ve also registered some callbacks to control the sound level; this is a requirement of the Windows OS.  Since all my sounds are short, I cheated, and the callbacks do nothing.
As a short aside, the other option is Communications; it’s the only other setting that works in the background.  However, my ding sounds are too short and don’t work properly.  Existing music would be faded out so users could hear the sounds, but that happed too slowly; by the time my sound was faded in, it was already over.  [Update: I'm also told by People At Microsoft that the Communications settings is wrong, wrong, wrong unless you are actually a communications app]

Convergence problems

Except that this is not fully converged.  For the phone you have to add callbacks to Windows.Media.SystemMediaTransportControls (getting the current one with GetForCurrentView()); the two callbacks you need are PropertyChanged and ButtonPressed.  On Desktop, you add callbacks to Windows.Media.MediaControl and you need a callback for about six different things.

App Manifest Woes

This still doesn’t work; you need to update the Package.appmanifest to declare that you do background audio.  Here the blogs are simply wrong, and present XAML that works until you try to compile for RELEASE mode.
The correct XAML is:
<Extensions>
  <Extension Category="windows.backgroundTasks"
               
EntryPoint="DesktopCompanion.App">
    <BackgroundTasks>
      <Task Type="audio" />
    </BackgroundTasks>
  </Extension>
</Extensions>

Note the EntryPoint item; the blog posts about this all mention a StartPage, which is wrong in this context.
But we’re still not done.

Hidden windows, quiet timers

So we have a great solution, and the app will ding when it’s minimized.  We can even prove it; run the app, wait for the ding, run minimized, and wait for the ding again.
That test turns out to be flawed.  IF you start the timer and minimize it before it has a change to ding the first time, it won’t ding in the background.  You have to make a foreground sound first.
Solution: add  a very short quiet ding right when the app starts. 
I also discovered that setting the ding to be looping would solve the problem, but it seemed like the first go-around would not play and then the second and subsequent ones would.

Oh, so you like listening to music?

There’s one problem I haven’t solved.  If you do all this on the phone, the “ding” will automatically stop your music from running.  On full (desktop/laptop/tablet) Windows, the music will simply fade out, let the ding happen, and then come back.  On the phone it stops dead and doesn’t resume.
This might not be bad, but the ding is actually optional; you can run in muted mode.  I normally run muted; otherwise it would distract by work mates.
Solution:
  • Add a DingSilentlyOnce() that can do the silent ding
  • Call it at startup unless the user has the app muted
  • Call it when the user switches out of muted mode (either by pressing the mute button or by selecting a sound to play)
This still doesn’t solve the problem where the ding will stop my background music on the phone.

Conclusion

Making things go ding is only slightly painful until you need it to actually work, at which point four different changes are needed, none of which are properly documented.

No comments: