]>
Commit | Line | Data |
---|---|---|
1 | import threading | |
2 | import time | |
3 | ||
4 | from transitions.extensions import HierarchicalMachine as Machine | |
5 | from . import debug_print, error_print | |
6 | ||
7 | class Action: | |
8 | ACTION_TYPES = [ | |
9 | 'command', | |
10 | 'interrupt_wait', | |
11 | 'pause', | |
12 | 'play', | |
13 | 'seek', | |
14 | 'stop', | |
15 | 'stop_all_actions', | |
16 | 'unpause', | |
17 | 'volume', | |
18 | 'wait', | |
19 | ] | |
20 | ||
21 | STATES = [ | |
22 | 'initial', | |
23 | 'loading', | |
24 | 'failed', | |
25 | { | |
26 | 'name': 'loaded', | |
27 | 'children': ['running'] | |
28 | } | |
29 | ] | |
30 | ||
31 | TRANSITIONS = [ | |
32 | { | |
33 | 'trigger': 'load', | |
34 | 'source': 'initial', | |
35 | 'dest': 'loading' | |
36 | }, | |
37 | { | |
38 | 'trigger': 'fail', | |
39 | 'source': 'loading', | |
40 | 'dest': 'failed', | |
41 | 'after': 'poll_loaded' | |
42 | }, | |
43 | { | |
44 | 'trigger': 'success', | |
45 | 'source': 'loading', | |
46 | 'dest': 'loaded', | |
47 | 'after': 'poll_loaded' | |
48 | }, | |
49 | { | |
50 | 'trigger': 'run', | |
51 | 'source': 'loaded', | |
52 | 'dest': 'loaded_running', | |
53 | 'after': 'finish_action', | |
54 | # if a child has no transitions, then it is bubbled to the parent, | |
55 | # and we don't want that. Not useful in that machine precisely. | |
56 | 'conditions': ['is_loaded'] | |
57 | }, | |
58 | { | |
59 | 'trigger': 'finish_action', | |
60 | 'source': 'loaded_running', | |
61 | 'dest': 'loaded' | |
62 | } | |
63 | ] | |
64 | ||
65 | def __init__(self, action, key, **kwargs): | |
66 | Machine(model=self, states=self.STATES, | |
67 | transitions=self.TRANSITIONS, initial='initial', | |
68 | ignore_invalid_triggers=True, queued=True) | |
69 | ||
70 | self.action = action | |
71 | self.key = key | |
72 | self.mapping = key.parent | |
73 | self.arguments = kwargs | |
74 | self.sleep_event = None | |
75 | self.waiting_music = None | |
76 | ||
77 | def is_loaded_or_failed(self): | |
78 | return self.is_loaded(allow_substates=True) or self.is_failed() | |
79 | ||
80 | def callback_music_loaded(self, success): | |
81 | if success: | |
82 | self.success() | |
83 | else: | |
84 | self.fail() | |
85 | ||
86 | # Machine states / events | |
87 | def on_enter_loading(self): | |
88 | if self.action in self.ACTION_TYPES: | |
89 | if 'music' in self.arguments: | |
90 | self.arguments['music'].subscribe_loaded(self.callback_music_loaded) | |
91 | else: | |
92 | self.success() | |
93 | else: | |
94 | error_print("Unknown action {}".format(self.action)) | |
95 | self.fail() | |
96 | ||
97 | def on_enter_loaded_running(self): | |
98 | debug_print(self.description()) | |
99 | getattr(self, self.action)(**self.arguments) | |
100 | ||
101 | def poll_loaded(self): | |
102 | self.key.callback_action_ready(self, | |
103 | self.is_loaded(allow_substates=True)) | |
104 | ||
105 | # This one cannot be in the Machine state since it would be queued to run | |
106 | # *after* the wait is ended... | |
107 | def interrupt(self): | |
108 | if getattr(self, self.action + "_interrupt", None): | |
109 | return getattr(self, self.action + "_interrupt")(**self.arguments) | |
110 | ||
111 | # Helpers | |
112 | def music_list(self, music): | |
113 | if music is not None: | |
114 | return [music] | |
115 | else: | |
116 | return self.mapping.open_files.values() | |
117 | ||
118 | def description(self): | |
119 | if getattr(self, self.action + "_print", None): | |
120 | return getattr(self, self.action + "_print")(**self.arguments) | |
121 | else: | |
122 | return "unknown action {}".format(self.action) | |
123 | ||
124 | # Actions | |
125 | def command(self, command="", **kwargs): | |
126 | # FIXME: todo | |
127 | pass | |
128 | ||
129 | def pause(self, music=None, **kwargs): | |
130 | for music in self.music_list(music): | |
131 | if music.is_loaded_playing(): | |
132 | music.pause() | |
133 | ||
134 | def unpause(self, music=None, **kwargs): | |
135 | for music in self.music_list(music): | |
136 | if music.is_loaded_paused(): | |
137 | music.unpause() | |
138 | ||
139 | def play(self, music=None, fade_in=0, start_at=0, | |
140 | restart_if_running=False, volume=100, | |
141 | loop=0, **kwargs): | |
142 | for music in self.music_list(music): | |
143 | if restart_if_running: | |
144 | if music.is_in_use(): | |
145 | music.stop() | |
146 | music.play( | |
147 | volume=volume, | |
148 | fade_in=fade_in, | |
149 | start_at=start_at, | |
150 | loop=loop) | |
151 | elif not music.is_in_use(): | |
152 | music.play( | |
153 | volume=volume, | |
154 | fade_in=fade_in, | |
155 | start_at=start_at, | |
156 | loop=loop) | |
157 | ||
158 | def seek(self, music=None, value=0, delta=False, **kwargs): | |
159 | for music in self.music_list(music): | |
160 | music.seek(value=value, delta=delta) | |
161 | ||
162 | def interrupt_wait(self, wait_id=None): | |
163 | self.mapping.interrupt_wait(wait_id) | |
164 | ||
165 | def stop(self, music=None, fade_out=0, wait=False, | |
166 | set_wait_id=None, **kwargs): | |
167 | previous = None | |
168 | for music in self.music_list(music): | |
169 | if music.is_loaded_paused() or music.is_loaded_playing(): | |
170 | if previous is not None: | |
171 | previous.stop(fade_out=fade_out) | |
172 | previous = music | |
173 | else: | |
174 | music.stop(fade_out=fade_out) | |
175 | ||
176 | if previous is not None: | |
177 | self.waiting_music = previous | |
178 | previous.stop( | |
179 | fade_out=fade_out, | |
180 | wait=wait, | |
181 | set_wait_id=set_wait_id) | |
182 | ||
183 | def stop_all_actions(self, **kwargs): | |
184 | self.mapping.stop_all_running() | |
185 | ||
186 | def volume(self, music=None, value=100, fade=0, delta=False, **kwargs): | |
187 | if music is not None: | |
188 | music.set_volume(value, delta=delta, fade=fade) | |
189 | else: | |
190 | self.mapping.set_master_volume(value, delta=delta, fade=fade) | |
191 | ||
192 | def wait(self, duration=0, music=None, set_wait_id=None, **kwargs): | |
193 | if set_wait_id is not None: | |
194 | self.mapping.add_wait_id(set_wait_id, self) | |
195 | ||
196 | self.sleep_event = threading.Event() | |
197 | self.sleep_event_timer = threading.Timer(duration, self.sleep_event.set) | |
198 | ||
199 | if music is not None: | |
200 | music.wait_end() | |
201 | ||
202 | self.sleep_event_timer.start() | |
203 | self.sleep_event.wait() | |
204 | ||
205 | # Action messages | |
206 | def command_print(self, command="", **kwargs): | |
207 | return "running command {}".format(command) | |
208 | ||
209 | def interrupt_wait_print(self, wait_id=None, **kwargs): | |
210 | return "interrupt wait with id {}".format(wait_id) | |
211 | ||
212 | def pause_print(self, music=None, **kwargs): | |
213 | if music is not None: | |
214 | return "pausing « {} »".format(music.name) | |
215 | else: | |
216 | return "pausing all musics" | |
217 | ||
218 | def unpause_print(self, music=None, **kwargs): | |
219 | if music is not None: | |
220 | return "unpausing « {} »".format(music.name) | |
221 | else: | |
222 | return "unpausing all musics" | |
223 | ||
224 | def play_print(self, music=None, fade_in=0, start_at=0, | |
225 | restart_if_running=False, volume=100, loop=0, **kwargs): | |
226 | message = "starting " | |
227 | if music is not None: | |
228 | message += "« {} »".format(music.name) | |
229 | else: | |
230 | message += "all musics" | |
231 | ||
232 | if start_at != 0: | |
233 | message += " at {}s".format(start_at) | |
234 | ||
235 | if fade_in != 0: | |
236 | message += " with {}s fade_in".format(fade_in) | |
237 | ||
238 | message += " at volume {}%".format(volume) | |
239 | ||
240 | if loop > 0: | |
241 | message += " {} times".format(loop + 1) | |
242 | elif loop < 0: | |
243 | message += " in loop" | |
244 | ||
245 | if restart_if_running: | |
246 | message += " (restarting if already running)" | |
247 | ||
248 | return message | |
249 | ||
250 | def stop_print(self, music=None, fade_out=0, wait=False, | |
251 | set_wait_id=None, **kwargs): | |
252 | ||
253 | message = "stopping " | |
254 | if music is not None: | |
255 | message += "music « {} »".format(music.name) | |
256 | else: | |
257 | message += "all musics" | |
258 | ||
259 | if fade_out > 0: | |
260 | message += " with {}s fadeout".format(fade_out) | |
261 | if wait: | |
262 | if set_wait_id is not None: | |
263 | message += " (waiting the end of fadeout, with id {})"\ | |
264 | .format(set_wait_id) | |
265 | else: | |
266 | message += " (waiting the end of fadeout)" | |
267 | ||
268 | return message | |
269 | ||
270 | def stop_all_actions_print(self, **kwargs): | |
271 | return "stopping all actions" | |
272 | ||
273 | def seek_print(self, music=None, value=0, delta=False, **kwargs): | |
274 | if delta: | |
275 | if music is not None: | |
276 | return "moving music « {} » by {:+d}s" \ | |
277 | .format(music.name, value) | |
278 | else: | |
279 | return "moving all musics by {:+d}s" \ | |
280 | .format(value) | |
281 | else: | |
282 | if music is not None: | |
283 | return "moving music « {} » to position {}s" \ | |
284 | .format(music.name, value) | |
285 | else: | |
286 | return "moving all musics to position {}s" \ | |
287 | .format(value) | |
288 | ||
289 | def volume_print(self, music=None, | |
290 | value=100, delta=False, fade=0, **kwargs): | |
291 | message = "" | |
292 | if delta: | |
293 | if music is not None: | |
294 | message += "{:+d}% to volume of « {} »" \ | |
295 | .format(value, music.name) | |
296 | else: | |
297 | message += "{:+d}% to volume" \ | |
298 | .format(value) | |
299 | else: | |
300 | if music is not None: | |
301 | message += "setting volume of « {} » to {}%" \ | |
302 | .format(music.name, value) | |
303 | else: | |
304 | message += "setting volume to {}%" \ | |
305 | .format(value) | |
306 | ||
307 | if fade > 0: | |
308 | message += " with {}s fade".format(fade) | |
309 | ||
310 | return message | |
311 | ||
312 | def wait_print(self, duration=0, music=None, set_wait_id=None, **kwargs): | |
313 | message = "" | |
314 | if music is None: | |
315 | message += "waiting {}s" \ | |
316 | .format(duration) | |
317 | elif duration == 0: | |
318 | message += "waiting the end of « {} »" \ | |
319 | .format(music.name) | |
320 | else: | |
321 | message += "waiting the end of « {} » + {}s" \ | |
322 | .format(music.name, duration) | |
323 | ||
324 | if set_wait_id is not None: | |
325 | message += " (setting id = {})".format(set_wait_id) | |
326 | ||
327 | return message | |
328 | ||
329 | # Interruptions (only for non-"atomic" actions) | |
330 | def wait_interrupt(self, duration=0, music=None, **kwargs): | |
331 | if self.sleep_event is not None: | |
332 | self.sleep_event.set() | |
333 | self.sleep_event_timer.cancel() | |
334 | if music is not None: | |
335 | music.wait_event.set() | |
336 | ||
337 | def stop_interrupt(self, music=None, fade_out=0, wait=False, | |
338 | set_wait_id=None, **kwargs): | |
339 | if self.waiting_music is not None: | |
340 | self.waiting_music.wait_event.set() |