Skip to content

✨ NEW "Magic" module - allows to control efficient and easy sessions! #42

Merged
AndreMiras merged 1 commit intoAndreMiras:developfrom
TurboAnonym:develop
May 16, 2021
Merged

✨ NEW "Magic" module - allows to control efficient and easy sessions! #42
AndreMiras merged 1 commit intoAndreMiras:developfrom
TurboAnonym:develop

Conversation

@TurboAnonym
Copy link
Contributor

@TurboAnonym TurboAnonym commented May 1, 2021

old idea:
the setup will create an executable linking to pycaw.tools.app_mixer.
you can launch this mixer by using pycaw-app-mixer or pip -m pycaw.tools.app_mixer
this tool allows to change the volume and view changes to sessions.

This is what I was talking about ;)

Copy link
Owner

@AndreMiras AndreMiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I know it's WIP, but I couldn't help it.
Looking good already!
Made some early comments 😃

setup.py Outdated
'comtypes', 'enum34;python_version<"3.4"', 'psutil', 'future']
setup(name='pycaw',
version='20190904',
version='20210501',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch I haven't released for all this time!
I would not bump this as part as the PR.
And that's a good reminder for me to make a release soon actually 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha 😂

yeah of course- with all these brand new features 🤩

by the way: do you think we could just drop the python2 support?
I see a lot of big packages doing that... wouldn't it be time for that?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I've been wondering for Python 2 so I'm glad you ask.
Python 2 officially retired January last year I think https://pythonclock.org/
I think we can release this version to be the last to support Python 2 and then drop it straight after that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm does this mean ALL the new features need to be py2 compatible 😅

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question, but new examples are already not Python2 compatible right?
I'll try to release this week or weekend, hold your horses 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly i have no clue what works in Python 2 or not 😇 (I have never used it ^^)
... besides the obvious like print() and f strings.

So i guess its not quite 100% compatible...

I would be in for #team_lazy and drop the py2 support now 😂 its 2021!
We could leave the legacy release on github or something...

Copy link
Contributor Author

@TurboAnonym TurboAnonym left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you (really!) for all the feedback!

It helps a lot improving my coding ^^

setup.py Outdated
'comtypes', 'enum34;python_version<"3.4"', 'psutil', 'future']
setup(name='pycaw',
version='20190904',
version='20210501',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha 😂

yeah of course- with all these brand new features 🤩

by the way: do you think we could just drop the python2 support?
I see a lot of big packages doing that... wouldn't it be time for that?

@TurboAnonym TurboAnonym changed the title ✨ Added pycaw-app-mixer tool to controll live audio sessions ✨ Added pycaw-app-mixer tool to control live audio sessions May 1, 2021
@TurboAnonym TurboAnonym force-pushed the develop branch 3 times, most recently from d218a7a to da58f0c Compare May 2, 2021 22:53
@TurboAnonym
Copy link
Contributor Author

TurboAnonym commented May 2, 2021

I split the functionality into a new module named magic 😇

This is a simple example:

import pycaw.magic !

The new Magic module allows to easy access and control the sessions of apps without headache!

example for MagicApp

from pycaw.magic import MagicApp

def changed(volume):
	print("Callback")
	print(volume)

m = MagicApp("msedge.exe", volume_callback=changed)

# start app for example now and play something!
# of course it would also work if app was already running.
time.sleep(6)

print(m.state)
print(m.volume)

m.volume = 0.6
m.mute = True

the pycaw session mixer is implemeted via the

MagicSession

Copy link
Owner

@AndreMiras AndreMiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just gave it a first quick look even though it's WIP.
But maybe the magic module should come in another PR as current changes are big enough and already provide value.
What do you think?

@@ -0,0 +1,563 @@
"""
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure you will write unit tests for this new module, right? 😅 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm i started writing an example... that should do the trick in general right? ... of course not with msedge ... but similar

@TurboAnonym
Copy link
Contributor Author

"But maybe the magic module should come in another PR as current changes are big enough and already provide value.
What do you think?"

its more like in reverse ... the initial idea for the pull was to create a pycaw-app-mixer but now it depends on the magic module...
(i splitted it up and added more functionality)

so i guess i will drop the pycaw-app-mixer for now and this pull will be for the magic module.


I am contributing so much because i was searching for something like the magic module.
The closest to that was the initial pycaw. At first i didnt thought that a 'magic' way would ever exist to easily control the app volume - but here we are. The pycaw-app-mixer is more a fun by-product wich helped me understand windows audio sessions.


My total goal is for the magic module to support:

for now:

  • to control one or multiple sessions by app.exe (MagicApp)
  • to register for each new session a wrapper to be able to do something like the pycaw-app-mixer (MagicSession)

later:

  • to control the main speaker (not impl.)
  • to control a custom device (also mics) by name (not impl.)

... and this all easily without (much) knowledge about: com (sta, mta) windows audio sessions and the callbacks and interfaces and so on.

@TurboAnonym TurboAnonym changed the title ✨ Added pycaw-app-mixer tool to control live audio sessions ✨ NEW "Magic" module - allows to control efficient and easy sessions! May 4, 2021
@TurboAnonym
Copy link
Contributor Author

TurboAnonym commented May 4, 2021

@AndreMiras can you please give me your opinion on the TODOS in the code? ;)

Heres a simplified sketch - how the magic module works.

1Magic

high resolution as pdf:
1Magic.pdf

Copy link
Owner

@AndreMiras AndreMiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, it looks pretty good.
I hope you don't mind I made few more comments.
Also I'd rather have the CI pass green before merging anything

@TurboAnonym TurboAnonym force-pushed the develop branch 4 times, most recently from 221b4ec to 1296f8e Compare May 15, 2021 13:26
@TurboAnonym
Copy link
Contributor Author

There is now only one big problem left.

... And I cant really solve it - I bypassed it.

it has to do with all the
# XXX remove old session:
comments in the code.

The problem is: How to really "remove" an expired session.

When a session makes the callback OnStateChanged with AudioSessionState.Expired it should be ideally removed afterwards, so no ressources get wasted. But I could only implement the first step: to session.unregister_notification()

I then tried to remove any references to the _MagicRootSession which would result in releasing connected COMObjects like ._ctl2 (the <POINTER(IAudioSessionControl2)/>).
As soon as I released the final reference from the IAudioSessionControl2 via a callback the program broke. Broke in terms of no further COM callbacks where received... setting the volume would still work. I think I encountered a deadlock ...

From my still little understanding of COM, it has to do with keeping references and dont release them during a callback.

As it is stated in https://docs.microsoft.com/en-us/windows/win32/api/audiopolicy/nn-audiopolicy-iaudiosessionevents:

  • The client should never call the IAudioSessionControl::UnregisterAudioSessionNotification method during an event callback.
  • The client should never release the final reference on a WASAPI object during an event callback.

more lecture here ...
https://docs.microsoft.com/en-us/windows/win32/api/audiopolicy/nf-audiopolicy-iaudiosessioncontrol-unregisteraudiosessionnotification

bypass with a trash:

My idea was to implement a "trash" solution and keep in here references to deleted magic_root_sessions.
But emptying the trash (and thus releasing <POINTER(IAudioSessionControl2)/>) wont work in ANY callback, not even MagicManager.OnSessionCreated().

So in the current state the trash gets only emptied when the script is closed or when the user calls explicitly MagicManager.empty_trash()

I dont think that this is ideal. But it works. (even with 150 trashed sessions)

More details:

You can easily deactivate my "trash" solution by commenting:

MagicManager.remove_session ->
cls.expired_magic_root_sessions.add(magic_root_session)

Note:
There is a slight difference in the callback behavior, when the "trash" solution is deactivated:
When using MagicManager.magic_session(MagicSession) I enconter the deadlock after:

close a session -> start a session -> deadlock

When using MagicApp mode or MagicManager.activate_magic() (without MagicSession)

close a session -> deadlock

@TurboAnonym TurboAnonym marked this pull request as ready for review May 15, 2021 13:48
Copy link
Owner

@AndreMiras AndreMiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one last minor comment even though I think it looks good enough for a merge.
And also another thing that's important to me is to have at least some basic tests for the magic.py module. Since this is now part of the library I think it should be tested, specially since it has quite some logic.
I understand it's not an easy one, so I'm happy to help in that and also do it in a follow up PR.
But I wouldn't usually merge code with that amount of logic and no unit tests

Magic:
------
The new Magic module allows to easily access and control the sessions of apps without headache!

example for MagicApp
---

import time
from pycaw.magic import MagicApp

def changed(volume):
    print("callback: ", volume)

m = MagicApp({"msedge.exe", "app.exe"}, volume_callback=changed)

while m.state is None:
    print(f"Not present: {m}")
    time.sleep(2)

print(f"App session: {str(m.state)}")

print(m.mute)
print(m.volume)

m.volume = 0.6
time.sleep(3)
m.mute = True

print(m.mute)
print(m.volume)

---
@TurboAnonym
Copy link
Contributor Author

TurboAnonym commented May 16, 2021

Nice ;)
All done for this pull requests I guess ;)

I added with this push 'advanced_volume_callbacks', cleaned up some code comments and renamed some internal variables.
And made use of this 'guard clause pattern' thingy ... (thanks for the info ^^)

If you find something feel free to leave a comment ;)

See you in the next PR for unit testing.

@TurboAnonym
Copy link
Contributor Author

TurboAnonym commented May 16, 2021

One last thing:

i want to create a new issue regarding #42 (comment)
but i feel like there a way to many opened - yet solved issues.
Can you please close the solved ones, so feature requests and new issues dont get lost? 😇

Copy link
Owner

@AndreMiras AndreMiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hard work and thanks for addressing all the comment.
I still have hope for unit tests in some (near?) future

@AndreMiras AndreMiras merged commit 1afa3b8 into AndreMiras:develop May 16, 2021
@TurboAnonym
Copy link
Contributor Author

😉

#45

@gitagogaming
Copy link

gitagogaming commented Feb 14, 2022

First off, want to say thank you very much for this addition, its a huge help!

I do see its possible to get any potential application volumes,

I was wondering if it is possible to get a callback on the default audio cable set inside of windows as well, so when the default audio levels change or mute I can know.

@gitagogaming
Copy link

gitagogaming commented Feb 16, 2022

Also I've taken notice that MagicManager.MagicSession doesnt appear to be capturing all the proper processes?
is this due to there not being any available callback from windows?

Is there some way for me to add a new session?
This below allows me to get the proper sessions, but no callback is enabled.

global_states = []
sessions = AudioUtilities.GetAllSessions()
old_volume_list=[]
running = True
for s in sessions:
    if s not in global_states:
        try:
            global_states.append(s.Process.name())
        except AttributeError:
            pass
if old_volume_list != global_states:
    print( global_states)
    old_volume_list = global_states

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants