Skip to main content

How to observe and control Windows media sessions in C#

How to control Windows media sessions in C#

If you use Windows 10 and play music, you’ll most likely come across this dialog at some point:

Windows volume overlay
This is the media overlay, and it appears whenever you’re playing music and press a media key or you change the volume. This dialog uses the GlobalSystemMediaTransportControls family of APIs available on the Windows 10 Runtime API since version 1809. This API is usually restricted to UWP applications, however, the introduction of .NET 5 and the ability to target specific platforms in the project’s TargetFramework has made it available for use in any kind of application, like WPF or regular console programs.

Fetching information from the GSMTC API

The API’s entrypoint is the GlobalSystemMediaTransportControlsSessionManager. This class allows you to fetch all the current active sessions, as well as whichever one Windows thinks is the current one.

In order to be able to access this class at all you must first set your project’s TargetFramework to one that allows the use of this API, for example net5.0-windows10.0.19041.0.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    </PropertyGroup>
</Project>

Your project will now only be able to run on Windows, however you’ll have gained access to all the APIs under the Windows namespace.

You can now retrieve an instance of the GlobalSystemMediaTransportControlsSessionManager class, like so:

using System;
using Windows.Media.Control;

var manager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();

(Note: I recommend using var for these variables, as the GSMTC API’s class names are extremely long!)

This manager instance should be reused in the lifetime of your application, calling any of its methods when necessary. For instance, we can now fetch the current session and query the media info:

var currentSession = manager.GetCurrentSession();
var mediaInfo = await currentSession.TryGetMediaPropertiesAsync();

Console.WriteLine($"{mediaInfo.Title} by {mediaInfo.Artist}");

Reacting to playback changes

The GSMTC session API exposes a few events that let you react to certain changes, for example but not limited to:

  • Playback pause/resume
  • Playback position changed
  • Media changed (e.g. a different song is playing)
  • Shuffle/repeat option enabled/disabled

All of these changes are exposed through 3 events under the GSMTCSession class: MediaPropertiesChanged, PlaybackInfoChanged and TimelinePropertiesChanged.
To subscribe to any of these just attach an event handler to them, just like you would for any C# event:

currentSession.TimelinePropertiesChanged += (session, _) =>
{
    TimeSpan position = session.GetTimelineProperties().Position;

    Console.WriteLine($"Current position: {position:mm\\:ss}");
};

(Note: this event seems to only fire once about every 5 seconds, so you may have to apply some interpolation)

Controlling playback

Lastly, the GSMTC API exposes numerous methods that allow you to control the playback, including but not limited to:

  • Pause/resume/stop playback
  • Set playback position
  • Skip to previous/next media

You can check the full list of supported methods here.

Knowing this it’s extremely easy to perform any actions we want, we just need to call the appropiate method under our GSMTCSession instance:

bool success = await currentSession.TryPauseAsync();

As you can see, all of the Try... methods return a boolean when awaited that indicate whether the action was successful or not. This could be useful in case a certain action is not available at the moment and you get the chance to inform the user. However, you do not have to rely solely on the return value of these methods, as the GSMTC has one last trick up its sleeve:

Checking available features

The GlobalSystemMediaTransportControlsSessionPlaybackInfo type contains information about the actions that the current session supports. An instance of this type can be retrieved through the GetPlaybackInfo method and its return value’s Controls property.

Comments

Popular posts from this blog

Hangfire: setup and usage

How to setup Hangfire, a job scheduler for .NET Installing Hangfire Setting up Hangfire Setting up storage MySQL Storage In-memory storage Running jobs Fire-and-forget jobs Delayed jobs Continuation jobs Recurring jobs ASP.NET Core job integration Conclusion Hangfire is a job scheduler for .NET and .NET Core that lets you run jobs in the background. It features various different job types: Fire-and-forget jobs are executed only once , shortly after creation. Delayed jobs are very similar to fire-and-forget jobs but they wait for a specified amount of time before running. Continuation jobs, which run after their parent job has finished . Recurring jobs, perhaps the most interesting of all, run repeatedly on an interval . If you like Unix’s cron you’ll feel right at home, as this kind of job lets you specify the job interval as a cron expression . If you have never used this I recommend crontab.guru , it has a live expression editor, as well as some examp...

ConditionalWeakTable, what does it do?

C#'s ConditionalWeakTable, what does it do? C# has many lesser known features, some more useful than others, and one of them is the ConditionalWeakTable<TKey, TValue> (keep in mind that TKey and TValue must be reference types). You can think of this type as a dictionary where the keys are weakly referenced, meaning that they won’t count when the GC checks if the object has to be collected. Additionally, when the keys do eventually get collected by the GC, that entry will get removed from the dictionary. This means that you can attach arbitrary objects to any object, allowing you to do something like this: public static class Extensions { private static ConditionalWeakTable < object , dynamic > Table = new ConditionalWeakTable < object , dynamic > ( ) ; public static dynamic Data ( this object obj ) { if ( ! Table . TryGetValue ( obj , out var dyn ) ) Table . Add ( obj , dyn = new ExpandoObject ( )...