-
Notifications
You must be signed in to change notification settings - Fork 3
/
pp_audioplayer.py
489 lines (394 loc) · 18.5 KB
/
pp_audioplayer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
import os
from pp_mplayerdriver import MplayerDriver
from pp_player import Player
from pp_audiomanager import AudioManager
class AudioPlayer(Player):
"""
plays an audio track using mplayer against a coloured backgroud and image
track can be paused and interrupted
"""
# audio mixer matrix settings
_LEFT = "channels=2:1:0:0:1:1"
_RIGHT = "channels=2:1:0:1:1:0"
_STEREO = "channels=2"
# ***************************************
# EXTERNAL COMMANDS
# ***************************************
def __init__(self,
show_id,
showlist,
root,
canvas,
show_params,
track_params,
pp_dir,
pp_home,
pp_profile,
end_callback,
command_callback):
# use AudioManager
self.am=AudioManager()
# initialise items common to all players
Player.__init__( self,
show_id,
showlist,
root,
canvas,
show_params,
track_params ,
pp_dir,
pp_home,
pp_profile,
end_callback,
command_callback)
self.mon.trace(self,'')
# get duration limit (secs ) from profile
if self.show_params['type'] in ('liveshow','artliveshow'):
self.duration_text=''
else:
self.duration_text= self.track_params['duration']
# get audio device from profile.
if self.track_params['mplayer-audio'] != "":
self.mplayer_audio= self.track_params['mplayer-audio']
else:
self.mplayer_audio= self.show_params['mplayer-audio']
# get audio volume from profile.
if self.track_params['mplayer-volume'] != "":
mplayer_volume= self.track_params['mplayer-volume'].strip()
else:
mplayer_volume= self.show_params['mplayer-volume'].strip()
self.mplayer_volume_int=int(mplayer_volume)
self.volume_option= '-volume ' + str(self.mplayer_volume_int)
# get speaker from profile
if self.track_params['audio-speaker'] != "":
self.audio_speaker= self.track_params['audio-speaker']
else:
self.audio_speaker= self.show_params['audio-speaker']
if self.audio_speaker == 'left':
self.speaker_option=AudioPlayer._LEFT
elif self.audio_speaker == 'right':
self.speaker_option=AudioPlayer._RIGHT
else:
self.speaker_option=AudioPlayer._STEREO
if self.track_params['mplayer-other-options'] != '':
self.mplayer_other_options= self.track_params['mplayer-other-options']
else:
self.mplayer_other_options= self.show_params['mplayer-other-options']
if self.track_params['pause-timeout'] != '':
pause_timeout_text= self.track_params['pause-timeout']
else:
pause_timeout_text= self.show_params['pause-timeout']
if pause_timeout_text.isdigit():
self.pause_timeout= int(pause_timeout_text)
else:
self.pause_timeout=0
# initialise the state and signals
self.tick_timer=None
self.quit_signal=False
self.play_state='initialised'
self.waiting=False
self.pause_timer=None
# LOAD - creates and mplayer instance, loads a track and then pause
def load(self,track,loaded_callback,enable_menu):
# instantiate arguments
self.track=track
self.loaded_callback=loaded_callback #callback when loaded
self.mon.trace(self,'')
#get sink name and check device is connected
status,message,self.mplayer_sink = self.am.get_sink(self.mplayer_audio)
if status == 'error':
self.mon.err(self,message)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error',message)
return
if not self.am.sink_connected(self.mplayer_sink):
self.mon.err(self,'audio device not connected - '+self.mplayer_audio + '/n sink: '+self.mplayer_sink)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error','audio device not connected')
return
if self.mplayer_volume_int<0 or self.mplayer_volume_int>100:
self.mon.err(self,'audio volume out of range: - '+str(self.mplayer_volume_int))
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error','audio volume out of range')
return
# do common bits of load
Player.pre_load(self)
# load the plugin, this may modify self.track and enable the plugin drawign to canvas
if self.track_params['plugin'] != '':
status,message=self.load_plugin()
if status == 'error':
self.mon.err(self,message)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error',message)
return
# load the images and text
status,message=self.load_x_content(enable_menu)
if status == 'error':
self.mon.err(self,message)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error',message)
return
if self.duration_text != '':
status,message,duration100=Player.parse_duration(self.duration_text)
if status =='error':
self.mon.err(self,message)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error','track file not found: ' + track)
return
self.duration_limit= 2* duration100
else:
self.duration_limit=-1
#print (self.duration_limit)
if track !='' and self.duration_limit!=0 and not os.path.exists(track):
self.mon.err(self,"Track file not found: "+ track)
self.play_state='load-failed'
if self.loaded_callback is not None:
self.loaded_callback('error','track file not found: ' + track)
return
# just create instance of mplayer don't bother with any pre-load
self.mplayer=MplayerDriver(self.canvas,self.pp_dir)
self.play_state='loaded'
self.mon.log(self,"<Track loaded from show Id: "+ str(self.show_id))
if self.loaded_callback is not None:
self.loaded_callback('loaded','audio track loaded')
def unload(self):
self.mon.trace(self,'')
self.mplayer=None
self.play_state='unloaded'
def show(self,ready_callback,finished_callback,closed_callback):
# instantiate arguments
self.ready_callback=ready_callback # callback when ready to show video
self.finished_callback=finished_callback # callback when finished showing
self.closed_callback=closed_callback
self.mon.trace(self,'')
# do animation begin etc.
Player.pre_show(self)
# start playing the track.
self.mon.log(self,">start playing track: "+ str(self.show_id))
if self.duration_limit != 0:
self.start_play_state_machine_show()
else:
self.mplayer=None
if self.closed_callback is not None:
self.closed_callback('normal','end with zero duration')
# CLOSE - nothing to do in audioplayer - x content is removed by ready callback
def close(self,closed_callback):
self.closed_callback=closed_callback
self.mon.log(self,">close received from show Id: "+ str(self.show_id))
self.mon.trace(self,'')
self.start_play_state_machine_close()
def input_pressed(self,symbol):
if symbol == 'inc-volume':
self.control('0')
elif symbol== 'dec-volume':
self.control('9')
elif symbol[0:6] == 'mplay-':
self.control(symbol[6])
elif symbol == 'unmute':
self.unmute()
elif symbol == 'mute':
self.mute()
elif symbol == 'pause-on':
self.pause_on()
elif symbol == 'pause-off':
self.pause_off()
elif symbol == 'pause':
self.pause()
elif symbol == 'stop':
self.stop()
def mute(self):
if self.play_state == 'showing' and self.track != '':
self.mplayer.mute()
return True
else:
self.mon.log(self,"!<mute rejected " + self.play_state)
return False
def unmute(self):
if self.play_state == 'showing' and self.track != '':
self.mplayer.unmute()
return True
else:
self.mon.log(self,"!<unmute rejected " + self.play_state)
return False
# pause on
def pause_on(self):
if self.play_state == 'showing' and self.track != '':
self.mplayer.pause_on()
if self.mplayer.paused is True and self.pause_timeout>0:
# kick off the pause teimeout timer
# print("!!pause on")
self.pause_timer=self.canvas.after(self.pause_timeout*1000,self.pause_timeout_callback)
return True
else:
self.mon.log(self,"!<pause on rejected")
return False
# pause off
def pause_off(self):
if self.play_state == 'showing' and self.track != '':
self.mplayer.pause_off()
if self.mplayer.paused is False:
# print("!!pause off")
# cancel the pause timer
if self.pause_timer != None:
self.canvas.after_cancel(self.pause_timer)
self.pause_timer=None
return True
else:
self.mon.log(self,"!<pause off rejected")
return False
# toggle pause
def pause(self):
if self.play_state == 'showing' and self.track != '':
self.mplayer.pause()
if self.mplayer.paused is True and self.pause_timeout>0:
# kick off the pause teimeout timer
# print("!!toggle pause on")
self.pause_timer=self.canvas.after(self.pause_timeout*1000,self.pause_timeout_callback)
else:
# cancel the pause timer
if self.pause_timer != None:
# print("!!toggle pause off")
self.canvas.after_cancel(self.pause_timer)
self.pause_timer=None
return True
else:
self.mon.log(self,"!<pause rejected")
return False
def pause_timeout_callback(self):
# print("!!callback pause off")
self.pause_off()
self.pause_timer=None
# other control when playing, not currently used
def control(self,char):
if self.play_state == 'showing' and self.track != ''and char not in ('q'):
self.mon.log(self,"> send control to mplayer: "+ char)
self.mplayer.control(char)
return True
else:
self.mon.log(self,"!<control rejected")
return False
# respond to normal stop
def stop(self):
# cancel the pause timer
if self.pause_timer != None:
self.canvas.after_cancel(self.pause_timer)
self.pause_timer=None
# send signal to stop the track to the state machine
self.mon.log(self,">stop received")
if self.play_state in ('starting','showing'):
self.quit_signal=True
# ***************************************
# sequencing
# ***************************************
"""
self. play_state controls the playing sequence, it has the following values.
- initialised - _init__ done
- loaded - mplayer instance created, no pre-load for audio tracks
- starting - mplayer process is running but is not yet able to receive controls
- showing - playing a track, controls can be sent
- closing - mplayer is doing its termination, controls cannot be sent
- waiting - audio file has finished, witing for duration
- closed - the mplayer process is closed after a track is played or duration is exceeded
"""
def start_play_state_machine_show(self):
if self.play_state == 'loaded':
# initialise all the state machine variables
self.duration_count = 0
if self.mplayer_sink != '':
driver_option=' -ao pulse::'+self.mplayer_sink
else:
driver_option=''
#print ('pulse audio',self.mplayer_audio,driver_option)
# play the track
options = ' '+ self.mplayer_other_options + ' '+ driver_option + ' ' + self.volume_option + ' -af '+ self.speaker_option + ' '
# print (options)
if self.track != '':
self.mplayer.play(self.track,options)
self.mon.log (self,'Playing audio track from show Id: '+ str(self.show_id))
self.play_state='starting'
else:
# no track to play so cannot rely on mplayer starting signal
self.play_state='showing'
# and start polling for state changes and count duration
self.tick_timer=self.canvas.after(50, self.play_state_machine)
else:
self.mon.fatal(self,'illegal state in show method ' + self.play_state)
self.play_state='show-failed'
if self.finished_callback is not None:
self.finished_callback('error','illegal state in show method ' + self.play_state)
def start_play_state_machine_close(self):
self.quit_signal=True
# print 'start close state machine close',self.play_state
self.tick_timer=self.canvas.after(1, self.play_state_machine)
def play_state_machine(self):
self.duration_count+=1
# self.mon.log(self," State machine: " + self.play_state)
if self.play_state == 'closed':
# self.mon.log(self," State machine: " + self.play_state)
pass
elif self.play_state == 'starting':
# self.mon.log(self," State machine: " + self.play_state)
# if mplayer has started and can accept runtime commands change to showing state
if self.mplayer.start_play_signal is True:
self.mon.log(self," <start play signal received from mplayer")
self.mplayer.start_play_signal=False
self.play_state='showing'
self.mon.log(self," State machine: go to showing")
self.tick_timer=self.canvas.after(50, self.play_state_machine)
elif self.play_state == 'showing':
if self.waiting is True:
# self.mon.log(self," State machine: " + self.play_state + ' ' + self.waiting)
if self.quit_signal is True or (self.duration_limit>0 and self.duration_count>self.duration_limit):
self.mon.log(self," Service stop required signal or timeout from wait")
self.quit_signal=False
self.waiting=False
self.play_state = 'closed'
if self.closed_callback is not None:
self.closed_callback('normal','wait is finished')
else:
self.tick_timer=self.canvas.after(50, self.play_state_machine)
# self.mon.log(self," State machine: " + self.play_state)
# service any queued stop signals
elif self.quit_signal is True or (self.duration_limit>0 and self.duration_count>self.duration_limit):
self.mon.log(self," Service stop required signal or timeout")
# self.quit_signal=False
if self.track != '':
self.mplayer.stop()
self.play_state = 'closing'
self.mon.log(self," State machine: closing due to quit or duration with track to play")
self.tick_timer=self.canvas.after(50, self.play_state_machine)
else:
self.mon.log(self," State machine: closed due to quit or duration with NO track to play")
self.play_state='closed'
if self.closed_callback is not None:
self.closed_callback('normal','user quit or duration NO track to play')
# mplayer reports it is finishing at end of track so change to ending state
elif self.track != '' and self.mplayer.end_play_signal:
self.mon.log(self," <end play signal received")
self.mon.log(self," <end detected at: " + str(self.mplayer.audio_position))
self.play_state = 'closing'
self.tick_timer=self.canvas.after(50, self.play_state_machine)
else:
self.tick_timer=self.canvas.after(50, self.play_state_machine)
elif self.play_state == 'closing':
# self.mon.log(self," State machine: " + self.play_state)
# if spawned process has closed can change to closed state
if self.mplayer.is_running() is False:
self.mon.log(self," <mplayer process is dead")
# if still need to wait for duration change to waiting state
if self.duration_limit>0 and self.duration_count<self.duration_limit:
self.play_state= 'showing'
self.waiting=True
self.tick_timer=self.canvas.after(50, self.play_state_machine)
else:
self.play_state = 'closed'
if self.closed_callback is not None:
self.closed_callback('normal','mplayer dead')
else:
self.tick_timer=self.canvas.after(50, self.play_state_machine)