Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added IAudioSessionEvents callbacks & Example #36

Merged
merged 4 commits into from
Apr 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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