]>
Commit | Line | Data |
---|---|---|
0deb82a5 | 1 | import threading |
be27763f IB |
2 | import time |
3 | ||
b7ca3fc2 IB |
4 | from transitions.extensions import HierarchicalMachine as Machine |
5 | from . import debug_print, error_print | |
a24c34bc | 6 | |
be27763f | 7 | class Action: |
b7ca3fc2 | 8 | ACTION_TYPES = [ |
be27763f | 9 | 'command', |
3aaddc9d | 10 | 'interrupt_wait', |
be27763f IB |
11 | 'pause', |
12 | 'play', | |
52d58baf | 13 | 'seek', |
be27763f IB |
14 | 'stop', |
15 | 'stop_all_actions', | |
9de92b6d | 16 | 'unpause', |
be27763f IB |
17 | 'volume', |
18 | 'wait', | |
19 | ] | |
20 | ||
b7ca3fc2 IB |
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', | |
e55b29bb IB |
40 | 'dest': 'failed', |
41 | 'after': 'poll_loaded' | |
b7ca3fc2 IB |
42 | }, |
43 | { | |
44 | 'trigger': 'success', | |
45 | 'source': 'loading', | |
e55b29bb IB |
46 | 'dest': 'loaded', |
47 | 'after': 'poll_loaded' | |
b7ca3fc2 IB |
48 | }, |
49 | { | |
50 | 'trigger': 'run', | |
51 | 'source': 'loaded', | |
52 | 'dest': 'loaded_running', | |
e55b29bb IB |
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'] | |
b7ca3fc2 IB |
57 | }, |
58 | { | |
59 | 'trigger': 'finish_action', | |
60 | 'source': 'loaded_running', | |
61 | 'dest': 'loaded' | |
62 | } | |
63 | ] | |
64 | ||
be27763f | 65 | def __init__(self, action, key, **kwargs): |
b7ca3fc2 IB |
66 | Machine(model=self, states=self.STATES, |
67 | transitions=self.TRANSITIONS, initial='initial', | |
68 | ignore_invalid_triggers=True, queued=True) | |
be27763f | 69 | |
b7ca3fc2 | 70 | self.action = action |
be27763f | 71 | self.key = key |
1b4b78f5 | 72 | self.mapping = key.parent |
be27763f | 73 | self.arguments = kwargs |
0deb82a5 | 74 | self.sleep_event = None |
b7ca3fc2 | 75 | self.waiting_music = None |
be27763f | 76 | |
e55b29bb IB |
77 | def is_loaded_or_failed(self): |
78 | return self.is_loaded(allow_substates=True) or self.is_failed() | |
b7ca3fc2 | 79 | |
e55b29bb | 80 | def callback_music_loaded(self, success): |
b7ca3fc2 IB |
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: | |
e55b29bb | 90 | self.arguments['music'].subscribe_loaded(self.callback_music_loaded) |
b7ca3fc2 IB |
91 | else: |
92 | self.success() | |
be27763f | 93 | else: |
b7ca3fc2 IB |
94 | error_print("Unknown action {}".format(self.action)) |
95 | self.fail() | |
96 | ||
b7ca3fc2 | 97 | def on_enter_loaded_running(self): |
a24c34bc | 98 | debug_print(self.description()) |
9de92b6d | 99 | getattr(self, self.action)(**self.arguments) |
be27763f | 100 | |
e55b29bb IB |
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): | |
0deb82a5 IB |
108 | if getattr(self, self.action + "_interrupt", None): |
109 | return getattr(self, self.action + "_interrupt")(**self.arguments) | |
110 | ||
b7ca3fc2 | 111 | # Helpers |
29597680 | 112 | def music_list(self, music): |
be27763f | 113 | if music is not None: |
29597680 | 114 | return [music] |
be27763f | 115 | else: |
1b4b78f5 | 116 | return self.mapping.open_files.values() |
29597680 | 117 | |
b7ca3fc2 IB |
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 | ||
c80ff6dc | 124 | # Actions |
2e404903 | 125 | def command(self, command="", **kwargs): |
c80ff6dc IB |
126 | # FIXME: todo |
127 | pass | |
128 | ||
2e404903 | 129 | def pause(self, music=None, **kwargs): |
29597680 IB |
130 | for music in self.music_list(music): |
131 | if music.is_loaded_playing(): | |
132 | music.pause() | |
be27763f | 133 | |
2e404903 | 134 | def unpause(self, music=None, **kwargs): |
29597680 IB |
135 | for music in self.music_list(music): |
136 | if music.is_loaded_paused(): | |
137 | music.unpause() | |
9de92b6d | 138 | |
2e404903 IB |
139 | def play(self, music=None, fade_in=0, start_at=0, |
140 | restart_if_running=False, volume=100, | |
141 | loop=0, **kwargs): | |
c80ff6dc | 142 | for music in self.music_list(music): |
0e5d59f7 | 143 | if restart_if_running: |
20586193 | 144 | if music.is_in_use(): |
0e5d59f7 | 145 | music.stop() |
2e404903 IB |
146 | music.play( |
147 | volume=volume, | |
148 | fade_in=fade_in, | |
149 | start_at=start_at, | |
150 | loop=loop) | |
20586193 | 151 | elif not music.is_in_use(): |
2e404903 IB |
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): | |
52d58baf | 159 | for music in self.music_list(music): |
2e404903 | 160 | music.seek(value=value, delta=delta) |
52d58baf | 161 | |
3aaddc9d IB |
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): | |
1b4b78f5 | 167 | previous = None |
29597680 IB |
168 | for music in self.music_list(music): |
169 | if music.is_loaded_paused() or music.is_loaded_playing(): | |
1b4b78f5 | 170 | if previous is not None: |
2e404903 | 171 | previous.stop(fade_out=fade_out) |
1b4b78f5 | 172 | previous = music |
51322669 IB |
173 | else: |
174 | music.stop(fade_out=fade_out) | |
1b4b78f5 IB |
175 | |
176 | if previous is not None: | |
b7ca3fc2 | 177 | self.waiting_music = previous |
3aaddc9d IB |
178 | previous.stop( |
179 | fade_out=fade_out, | |
180 | wait=wait, | |
181 | set_wait_id=set_wait_id) | |
be27763f IB |
182 | |
183 | def stop_all_actions(self, **kwargs): | |
1b4b78f5 | 184 | self.mapping.stop_all_running() |
be27763f | 185 | |
aee1334c | 186 | def volume(self, music=None, value=100, fade=0, delta=False, **kwargs): |
0e5d59f7 | 187 | if music is not None: |
aee1334c | 188 | music.set_volume(value, delta=delta, fade=fade) |
0e5d59f7 | 189 | else: |
a8340c5d | 190 | self.mapping.set_master_volume(value, delta=delta, fade=fade) |
be27763f | 191 | |
3aaddc9d IB |
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 | ||
0deb82a5 | 196 | self.sleep_event = threading.Event() |
e55b29bb | 197 | self.sleep_event_timer = threading.Timer(duration, self.sleep_event.set) |
0deb82a5 IB |
198 | |
199 | if music is not None: | |
b86db9f1 | 200 | music.wait_end() |
be27763f | 201 | |
e55b29bb | 202 | self.sleep_event_timer.start() |
0deb82a5 IB |
203 | self.sleep_event.wait() |
204 | ||
205 | # Action messages | |
2e404903 | 206 | def command_print(self, command="", **kwargs): |
be27763f IB |
207 | return "running command {}".format(command) |
208 | ||
3aaddc9d IB |
209 | def interrupt_wait_print(self, wait_id=None, **kwargs): |
210 | return "interrupt wait with id {}".format(wait_id) | |
211 | ||
2e404903 | 212 | def pause_print(self, music=None, **kwargs): |
be27763f | 213 | if music is not None: |
9de92b6d | 214 | return "pausing « {} »".format(music.name) |
be27763f IB |
215 | else: |
216 | return "pausing all musics" | |
217 | ||
2e404903 | 218 | def unpause_print(self, music=None, **kwargs): |
9de92b6d IB |
219 | if music is not None: |
220 | return "unpausing « {} »".format(music.name) | |
221 | else: | |
222 | return "unpausing all musics" | |
223 | ||
2e404903 IB |
224 | def play_print(self, music=None, fade_in=0, start_at=0, |
225 | restart_if_running=False, volume=100, loop=0, **kwargs): | |
be27763f IB |
226 | message = "starting " |
227 | if music is not None: | |
9de92b6d | 228 | message += "« {} »".format(music.name) |
be27763f | 229 | else: |
c80ff6dc | 230 | message += "all musics" |
be27763f IB |
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 | ||
6f4944c1 IB |
240 | if loop > 0: |
241 | message += " {} times".format(loop + 1) | |
242 | elif loop < 0: | |
243 | message += " in loop" | |
244 | ||
be27763f IB |
245 | if restart_if_running: |
246 | message += " (restarting if already running)" | |
247 | ||
248 | return message | |
249 | ||
3aaddc9d IB |
250 | def stop_print(self, music=None, fade_out=0, wait=False, |
251 | set_wait_id=None, **kwargs): | |
252 | ||
1b4b78f5 | 253 | message = "stopping " |
be27763f | 254 | if music is not None: |
1b4b78f5 | 255 | message += "music « {} »".format(music.name) |
be27763f | 256 | else: |
1b4b78f5 IB |
257 | message += "all musics" |
258 | ||
259 | if fade_out > 0: | |
260 | message += " with {}s fadeout".format(fade_out) | |
261 | if wait: | |
3aaddc9d IB |
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)" | |
1b4b78f5 IB |
267 | |
268 | return message | |
be27763f | 269 | |
0e5d59f7 | 270 | def stop_all_actions_print(self, **kwargs): |
be27763f IB |
271 | return "stopping all actions" |
272 | ||
2e404903 | 273 | def seek_print(self, music=None, value=0, delta=False, **kwargs): |
52d58baf IB |
274 | if delta: |
275 | if music is not None: | |
2e404903 IB |
276 | return "moving music « {} » by {:+d}s" \ |
277 | .format(music.name, value) | |
52d58baf | 278 | else: |
2e404903 IB |
279 | return "moving all musics by {:+d}s" \ |
280 | .format(value) | |
52d58baf IB |
281 | else: |
282 | if music is not None: | |
2e404903 IB |
283 | return "moving music « {} » to position {}s" \ |
284 | .format(music.name, value) | |
52d58baf | 285 | else: |
2e404903 IB |
286 | return "moving all musics to position {}s" \ |
287 | .format(value) | |
52d58baf | 288 | |
aee1334c IB |
289 | def volume_print(self, music=None, |
290 | value=100, delta=False, fade=0, **kwargs): | |
291 | message = "" | |
8e50011c | 292 | if delta: |
1b4b78f5 | 293 | if music is not None: |
aee1334c | 294 | message += "{:+d}% to volume of « {} »" \ |
2e404903 | 295 | .format(value, music.name) |
1b4b78f5 | 296 | else: |
aee1334c | 297 | message += "{:+d}% to volume" \ |
2e404903 | 298 | .format(value) |
be27763f | 299 | else: |
1b4b78f5 | 300 | if music is not None: |
aee1334c | 301 | message += "setting volume of « {} » to {}%" \ |
2e404903 | 302 | .format(music.name, value) |
1b4b78f5 | 303 | else: |
aee1334c | 304 | message += "setting volume to {}%" \ |
2e404903 | 305 | .format(value) |
be27763f | 306 | |
a8340c5d | 307 | if fade > 0: |
aee1334c IB |
308 | message += " with {}s fade".format(fade) |
309 | ||
310 | return message | |
311 | ||
3aaddc9d IB |
312 | def wait_print(self, duration=0, music=None, set_wait_id=None, **kwargs): |
313 | message = "" | |
0deb82a5 | 314 | if music is None: |
3aaddc9d | 315 | message += "waiting {}s" \ |
2e404903 | 316 | .format(duration) |
0deb82a5 | 317 | elif duration == 0: |
3aaddc9d | 318 | message += "waiting the end of « {} »" \ |
2e404903 | 319 | .format(music.name) |
0deb82a5 | 320 | else: |
3aaddc9d | 321 | message += "waiting the end of « {} » + {}s" \ |
2e404903 | 322 | .format(music.name, duration) |
be27763f | 323 | |
3aaddc9d IB |
324 | if set_wait_id is not None: |
325 | message += " (setting id = {})".format(set_wait_id) | |
326 | ||
327 | return message | |
be27763f | 328 | |
b7ca3fc2 | 329 | # Interruptions (only for non-"atomic" actions) |
2e404903 | 330 | def wait_interrupt(self, duration=0, music=None, **kwargs): |
0deb82a5 IB |
331 | if self.sleep_event is not None: |
332 | self.sleep_event.set() | |
e55b29bb | 333 | self.sleep_event_timer.cancel() |
0deb82a5 IB |
334 | if music is not None: |
335 | music.wait_event.set() | |
336 | ||
b7ca3fc2 IB |
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() |