Skip to content

Commit

Permalink
Added IAudioSessionEvents callbacks & Example (#36)
Browse files Browse the repository at this point in the history
Following "IAudioSessionEvents" callbacks are supported:
- OnSimpleVolumeChanged()
- OnStateChanged()
- OnSessionDisconnected()

Added an example to show the new features
  • Loading branch information
TurboAnonym authored Apr 24, 2021
1 parent 8ee0799 commit c034bc5
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 2 deletions.
99 changes: 99 additions & 0 deletions examples/session_callback_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Enter the right 'app_name' and play something to initiate a WASAPI session.
Then launch this file ;)
Requirements: Python >= 3.6 - f strings ;)
following "IAudioSessionEvents" callbacks are supported:
:: Gets called on volume and mute change:
IAudioSessionEvents.OnSimpleVolumeChanged()
:: Gets called on session state change (active/inactive/expired)
IAudioSessionEvents.OnStateChanged()
:: Gets called on for example Speaker unplug
IAudioSessionEvents.OnSessionDisconnected()
https://docs.microsoft.com/en-us/windows/win32/api/audiopolicy/nn-audiopolicy-iaudiosessionevents
"""
import time

from comtypes import COMError, COMObject

from pycaw.pycaw import AudioUtilities, IAudioSessionEvents

app_name = "msedge.exe"


class AudioSessionEvents(COMObject):
_com_interfaces_ = [IAudioSessionEvents]

def __init__(self):
# not necessary only to decode the returned int value
# see mmdeviceapi.h and audiopolicy.h
self.AudioSessionState = [
"AudioSessionStateInactive",
"AudioSessionStateActive",
"AudioSessionStateExpired"
]
self.AudioSessionDisconnectReason = [
"DisconnectReasonDeviceRemoval",
"DisconnectReasonServerShutdown",
"DisconnectReasonFormatChanged",
"DisconnectReasonSessionLogoff",
"DisconnectReasonSessionDisconnected",
"DisconnectReasonExclusiveModeOverride"
]

def OnSimpleVolumeChanged(self, NewVolume, NewMute, EventContext):
print(':: OnSimpleVolumeChanged callback')
print(f"NewVolume: {NewVolume}; "
f"NewMute: {NewMute}; "
f"EventContext: {EventContext.contents}")

def OnStateChanged(self, NewState):
print(':: OnStateChanged callback')
translate = self.AudioSessionState[NewState]
print(translate)

def OnSessionDisconnected(self, DisconnectReason):
print(':: OnSessionDisconnected callback')
translate = self.AudioSessionDisconnectReason[DisconnectReason]
print(translate)


def add_callback(app_name):
try:
sessions = AudioUtilities.GetAllSessions()
except COMError:
exit("No speaker set up")

# grap the right session
app_found = False
for session in sessions:
if session.Process and session.Process.name() == app_name:

app_found = True
callback = AudioSessionEvents()
# Adding the callback by accessing the
# IAudioSessionControl2 interface through ._ctl
session._ctl.RegisterAudioSessionNotification(callback)

if not app_found:
exit("Enter the right 'app_name', start it and play something")

print("Ready to go!")
print("Change the volume / mute state "
"/ close the app or unplug your speaker")
print("and watch the callbacks ;)\n")

try:
# wait 300 seconds for callbacks
time.sleep(300)
except KeyboardInterrupt:
pass
finally:
print("\nTschüss")


if __name__ == "__main__":
add_callback(app_name)
46 changes: 44 additions & 2 deletions pycaw/pycaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,46 @@ class IAudioEndpointVolume(IUnknown):
(['out'], POINTER(c_float), 'pfIncr')))


class IAudioSessionEvents(IUnknown):
_iid_ = GUID('{073d618c-490a-4f9f-9d18-7bec6fc21121}')
_methods_ = (
# HRESULT OnDisplayNameChanged(
# [in] LPCWSTR NewDisplayName,
# [in] LPCGUID EventContext);
COMMETHOD([], HRESULT, 'NotImpl1'),
# HRESULT OnIconPathChanged(
# [in] LPCWSTR NewIconPath,
# [in] LPCGUID EventContext);
COMMETHOD([], HRESULT, 'NotImpl2'),
# HRESULT OnSimpleVolumeChanged(
# [in] float NewVolume,
# [in] BOOL NewMute,
# [in] LPCGUID EventContext);
COMMETHOD([], HRESULT, 'OnSimpleVolumeChanged',
(['in'], c_float, 'NewVolume'),
(['in'], BOOL, 'NewMute'),
(['in'], POINTER(GUID), 'EventContext')),
# HRESULT OnChannelVolumeChanged(
# [in] DWORD ChannelCount,
# [in] float [] NewChannelVolumeArray,
# [in] DWORD ChangedChannel,
# [in] LPCGUID EventContext);
COMMETHOD([], HRESULT, 'NotImpl3'),
# HRESULT OnGroupingParamChanged(
# [in] LPCGUID NewGroupingParam,
# [in] LPCGUID EventContext);
COMMETHOD([], HRESULT, 'NotImpl4'),
# HRESULT OnStateChanged(
# AudioSessionState NewState);
COMMETHOD([], HRESULT, 'OnStateChanged',
(['in'], DWORD, 'NewState')),
# HRESULT OnSessionDisconnected(
# [in] AudioSessionDisconnectReason DisconnectReason);
COMMETHOD([], HRESULT, 'OnSessionDisconnected',
(['in'], DWORD, 'DisconnectReason')),
)


class IAudioSessionControl(IUnknown):
_iid_ = GUID('{F4B1A599-7266-4319-A8CA-E70ACB11E8CD}')
_methods_ = (
Expand All @@ -258,10 +298,12 @@ class IAudioSessionControl(IUnknown):
COMMETHOD([], HRESULT, 'NotImpl6'),
# HRESULT RegisterAudioSessionNotification(
# [in] IAudioSessionEvents *NewNotifications);
COMMETHOD([], HRESULT, 'NotImpl7'),
COMMETHOD([], HRESULT, 'RegisterAudioSessionNotification',
(['in'], POINTER(IAudioSessionEvents), 'NewNotifications')),
# HRESULT UnregisterAudioSessionNotification(
# [in] IAudioSessionEvents *NewNotifications);
COMMETHOD([], HRESULT, 'NotImpl8'))
COMMETHOD([], HRESULT, 'UnregisterAudioSessionNotification',
(['in'], POINTER(IAudioSessionEvents), 'NewNotifications')))


class IAudioSessionControl2(IAudioSessionControl):
Expand Down

0 comments on commit c034bc5

Please sign in to comment.