@@ -98,12 +98,19 @@ def _run_setup(self):
98
98
# Manually trigger the callback on startup
99
99
self ._on_current_session_changed (self ._session_manager , None , is_setup = True )
100
100
101
- def _on_current_session_changed (self , manager : SessionManager , args : SessionsChangedEventArgs , is_setup = False ):
101
+ def _on_current_session_changed (
102
+ self ,
103
+ manager : SessionManager ,
104
+ args : SessionsChangedEventArgs ,
105
+ is_setup = False ,
106
+ is_overridden = False ,
107
+ ):
102
108
if DEBUG :
103
109
self ._log .debug ('MediaCallback: _on_current_session_changed' )
104
110
105
111
with self ._current_session_lock :
106
- self ._current_session = manager .get_current_session ()
112
+ if not is_overridden :
113
+ self ._current_session = manager .get_current_session ()
107
114
108
115
if self ._current_session is not None :
109
116
@@ -121,7 +128,19 @@ def _on_current_session_changed(self, manager: SessionManager, args: SessionsCha
121
128
122
129
for callback in callbacks :
123
130
callback (self ._current_session is not None )
131
+
132
+ def _current_session_only (fn ):
133
+ """
134
+ Decorator to ensure that the function is only called if the session is the same as the current session
135
+ """
136
+
137
+ def wrapper (self : "WindowsMedia" , session : Session , * args , ** kwargs ):
138
+ with self ._current_session_lock :
139
+ if self ._are_same_sessions (session , self ._current_session ):
140
+ return fn (self , session , * args , ** kwargs )
141
+ return wrapper
124
142
143
+ @_current_session_only
125
144
def _on_playback_info_changed (self , session : Session , args : PlaybackInfoChangedEventArgs ):
126
145
if DEBUG :
127
146
self ._log .info ('MediaCallback: _on_playback_info_changed' )
@@ -136,6 +155,7 @@ def _on_playback_info_changed(self, session: Session, args: PlaybackInfoChangedE
136
155
for callback in callbacks :
137
156
callback (self ._playback_info )
138
157
158
+ @_current_session_only
139
159
def _on_timeline_properties_changed (self , session : Session , args : TimelinePropertiesChangedEventArgs ):
140
160
if DEBUG :
141
161
self ._log .info ('MediaCallback: _on_timeline_properties_changed' )
@@ -150,6 +170,7 @@ def _on_timeline_properties_changed(self, session: Session, args: TimelineProper
150
170
for callback in callbacks :
151
171
callback (self ._timeline_info )
152
172
173
+ @_current_session_only
153
174
def _on_media_properties_changed (self , session : Session , args : MediaPropertiesChangedEventArgs ):
154
175
if DEBUG :
155
176
self ._log .debug ('MediaCallback: _on_media_properties_changed' )
@@ -162,6 +183,7 @@ def _on_media_properties_changed(self, session: Session, args: MediaPropertiesCh
162
183
# Only for the initial timer based update, because it is called from an event loop
163
184
asyncio .create_task (self ._update_media_properties (session ))
164
185
186
+ @_current_session_only
165
187
async def _update_media_properties (self , session : Session ):
166
188
if DEBUG :
167
189
self ._log .debug ('MediaCallback: Attempting media info update' )
@@ -171,14 +193,9 @@ async def _update_media_properties(self, session: Session):
171
193
172
194
media_info = self ._properties_2_dict (media_info )
173
195
174
- # Skip initial change calls where the thumbnail is None. This prevents processing multiple updates.
175
- # Might prevent showing info for no-thumbnail media
176
- if media_info ['thumbnail' ] is None :
177
- if DEBUG :
178
- self ._log .debug ('MediaCallback: Skipping media info update: no thumbnail' )
179
- return
196
+ if media_info ['thumbnail' ] is not None :
197
+ media_info ['thumbnail' ] = await self .get_thumbnail (media_info ['thumbnail' ])
180
198
181
- media_info ['thumbnail' ] = await self .get_thumbnail (media_info ['thumbnail' ])
182
199
except Exception as e :
183
200
self ._log .error (f'MediaCallback: Error occurred whilst fetching media properties and thumbnail: { e } ' )
184
201
return
@@ -228,21 +245,40 @@ async def get_thumbnail(thumbnail_stream_reference: IRandomAccessStreamReference
228
245
finally :
229
246
# Close the stream
230
247
readable_stream .close ()
248
+
249
+ def _are_same_sessions (self , session1 : Session , session2 : Session ) -> bool :
250
+ return session1 .source_app_user_model_id == session2 .source_app_user_model_id
251
+
252
+ def switch_session (self , direction : int ):
253
+ sessions = self ._session_manager .get_sessions ()
254
+ if len (sessions ) == 0 :
255
+ return
231
256
232
- @staticmethod
233
- def play_pause ():
234
- user32 = ctypes .windll .user32
235
- user32 .keybd_event (VK_MEDIA_PLAY_PAUSE , 0 , KEYEVENTF_EXTENDEDKEY , 0 )
236
- user32 .keybd_event (VK_MEDIA_PLAY_PAUSE , 0 , KEYEVENTF_KEYUP , 0 )
257
+ with self ._current_session_lock :
258
+ current_session_idx = - 1
259
+ for i , session in enumerate (sessions ):
260
+ if self ._current_session is None or self ._are_same_sessions (session , self ._current_session ):
261
+ current_session_idx = i
262
+ break
263
+
264
+ idx = (current_session_idx + direction ) % len (sessions )
265
+ if self ._are_same_sessions (sessions [idx ], self ._current_session ):
266
+ return
267
+ self ._log .info (f"Switching to session { idx } ({ sessions [idx ].source_app_user_model_id } )" )
268
+ self ._current_session = sessions [idx ]
269
+ self ._on_current_session_changed (self ._session_manager , None , is_overridden = True )
237
270
238
- @staticmethod
239
- def prev ():
240
- user32 = ctypes .windll .user32
241
- user32 .keybd_event (VK_MEDIA_PREV_TRACK , 0 , KEYEVENTF_EXTENDEDKEY , 0 )
242
- user32 .keybd_event (VK_MEDIA_PREV_TRACK , 0 , KEYEVENTF_KEYUP , 0 )
271
+ def play_pause (self ):
272
+ with self ._current_session_lock :
273
+ if self ._current_session is not None :
274
+ self ._current_session .try_toggle_play_pause_async ()
243
275
244
- @staticmethod
245
- def next ():
246
- user32 = ctypes .windll .user32
247
- user32 .keybd_event (VK_MEDIA_NEXT_TRACK , 0 , KEYEVENTF_EXTENDEDKEY , 0 )
248
- user32 .keybd_event (VK_MEDIA_NEXT_TRACK , 0 , KEYEVENTF_KEYUP , 0 )
276
+ def prev (self ):
277
+ with self ._current_session_lock :
278
+ if self ._current_session is not None :
279
+ self ._current_session .try_skip_previous_async ()
280
+
281
+ def next (self ):
282
+ with self ._current_session_lock :
283
+ if self ._current_session is not None :
284
+ self ._current_session .try_skip_next_async ()
0 commit comments