forked from KenT2/pipresents-gapless
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pp_omxdriver.py
355 lines (287 loc) · 13.4 KB
/
pp_omxdriver.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
import signal
import os
import dbus
import subprocess
from threading import Thread
from time import sleep,time
from pp_utils import Monitor
"""
12/6/2016 - rewrite to use dbus
2/11/2016 - connection needs to wait for dbus filemane to be populated
2/11/2016 - remove busy wait for conection
omxdriver hides the detail of using the omxplayer command from videoplayer
This is meant to be used with videoplayer.py
Its easy to end up with many copies of omxplayer.bin running if this class is not used with care. use pp_videoplayer.py for a safer interface.
External commands
----------------------------
__init__ just creates the instance and initialises variables (e.g. omx=OMXDriver())
load - processes the track up to where it is ready to display, at this time it pauses.
show - plays the video from where 'prepare' left off by resuming from the pause.
play - plays a track (not used by gapless)
pause/unpause - pause on/off
toggle_pause - toggles pause
control - sends controls to omxplayer.bin while track is playing (use stop and pause instead of q and p)
stop - stops a video that is playing.
terminate - Stops a video playing. Used when aborting an application.
kill - kill of omxplayer when it hasn't terminated at the end of a track.
Signals
----------
The following signals are produced while a track is playing
self.start_play_signal = True when a track is ready to be shown
self.end_play_signal= True when a track has finished due to stop or because it has come to an end
self.end_play_reason reports the reason for the end
Also is_running() tests whether the sub-process running omxplayer is present.
"""
class OMXDriver(object):
_LAUNCH_CMD = '/usr/bin/omxplayer --no-keys ' # needs changing if user has installed his own version of omxplayer elsewhere
KEY_MAP = { '-': 17, '+': 18, '=': 18} # add more keys here, see popcornmix/omxplayer github
def __init__(self,widget,pp_dir):
self.widget=widget
self.pp_dir=pp_dir
self.mon=Monitor()
self.start_play_signal=False
self.end_play_signal=False
self.end_play_reason='nothing'
self.duration=0
self.video_position=0
self.pause_at_end_required=False
self.paused_at_end=False
self.pause_before_play_required=True
self.paused_at_start=False
self.paused=False
self.terminate_reason=''
# dbus and subprocess
self._process=None
self.__iface_root=None
self.__iface_props = None
self.__iface_player = None
# legacy
self.xbefore='not used - legacy'
self.xafter='not used - legacy'
def load(self, track, options,caller):
self.caller=caller
track= "'"+ track.replace("'","'\\''") + "'"
self.dbus_user = os.environ["USER"]
self.dbus_name = "org.mpris.MediaPlayer2.omxplayer"+str(int(time()*10))
self.omxplayer_cmd = OMXDriver._LAUNCH_CMD + options + " --dbus_name '"+ self.dbus_name + "' " + track
# self.mon.log(self, 'dbus user ' + self.dbus_user)
# self.mon.log(self, 'dbus name ' + self.dbus_name)
# print self.omxplayer_cmd
self.mon.log(self, "Send command to omxplayer: "+ self.omxplayer_cmd)
# self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/home/pi/pipresents/pp_logs/stdout.txt','a'),stderr=file('/home/pi/pipresents/pp_logs/stderr.txt','a'))
self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/dev/null','a'),stderr=file('/dev/null','a'))
self.pid=self._process.pid
# wait for omxplayer to start then start monitoring thread
self.dbus_tries = 0
self.omx_loaded = False
self._wait_for_dbus()
return
def _wait_for_dbus(self):
connect_success=self.__dbus_connect()
if connect_success is True:
# print 'SUCCESS'
self.mon.log(self,'connected to omxplayer dbus after ' + str(self.dbus_tries) + ' centisecs')
self.start_play_signal = True
if self.pause_before_play_required is True and self.paused_at_start is False:
self.pause('after load')
self.paused_at_start=True
# get duration of the track in microsecs
duration_success,duration=self.get_duration()
if duration_success is False:
self.duration=0
self.end_play_signal=True
self.end_play_reason='Failed to read duration - Not connected to omxplayer DBus'
else:
# pause before last frame
self.duration=duration-350000
# start the thread that is going to monitor output from omxplayer.
self._position_thread = Thread(target=self._monitor_status)
self._position_thread.start()
else:
self.dbus_tries+=1
self.widget.after(100,self._wait_for_dbus)
def _monitor_status(self):
while True:
retcode=self._process.poll()
# print 'in loop', self.pid, retcode
if retcode is not None:
# print 'process not running', retcode, self.pid
# process is not running because quit or natural end
self.end_play_signal=True
self.end_play_reason='nice_day'
break
else:
# test position ony when process is running, could have race condition
if self.paused_at_end is False:
# test position only when not paused for the end, in case process is dead
success, video_position = self.get_position()
if success is False:
# print 'process ended when reading video position ' + str(self.video_position)
pass # failure can happen because track has ended and process ended. Don't change video position
else:
self.video_position=video_position
# if timestamp is near the end then pause
if self.pause_at_end_required is True and self.video_position>self.duration: #microseconds
# print 'pausing at end'
self.pause(' at end of track')
self.paused_at_end=True
self.end_play_signal=True
self.end_play_reason='pause_at_end'
sleep(0.05)
def show(self,freeze_at_end_required):
self.pause_at_end_required=freeze_at_end_required
# unpause to start playing
self.unpause(' to start showing')
def control(self,char):
val = OMXDriver.KEY_MAP[char]
self.mon.log(self,'>control received and sent to omxplayer ' + str(self.pid))
if self.is_running():
try:
self.__iface_player.Action(dbus.Int32(val))
except dbus.exceptions.DBusException:
self.mon.warn(self,'Failed to send control - dbus exception')
return
else:
self.mon.warn(self,'Failed to send control - process not running')
return
def pause(self,reason):
self.mon.log(self,'pause received '+reason)
if not self.paused:
self.paused = True
self.mon.log(self,'not paused so send pause '+reason)
if self.is_running():
try:
self.__iface_player.Pause()
except dbus.exceptions.DBusException:
self.mon.warn(self,'Failed to send pause - dbus exception')
return
else:
self.mon.warn(self,'Failed to send pause - process not running')
return
def toggle_pause(self,reason):
self.mon.log(self,'toggle pause received '+ reason)
if not self.paused:
self.paused = True
else:
self.paused=False
if self.is_running():
try:
self.__iface_player.Action(16)
except dbus.exceptions.DBusException:
self.mon.warn(self,'Failed to toggle pause - dbus exception')
return
else:
self.mon.warn(self,'Failed to toggle pause - process not running')
return
def unpause(self,reason):
self.mon.log(self,'Unpause received '+ reason)
if self.paused:
self.paused = False
self.mon.log(self,'Is paused so Track unpaused '+ reason)
if self.is_running():
try:
self.__iface_player.Action(16)
except dbus.exceptions.DBusException as ex:
self.mon.warn(self,'Failed to send unpause - dbus exception: {}'.format(ex.get_dbus_message()))
return
# print 'unpause successful'
else:
self.mon.warn(self,'Failed to send pause - process not running')
return
def stop(self):
self.mon.log(self,'>stop received and quit sent to omxplayer ' + str(self.pid))
if self.is_running():
try:
self.__iface_root.Quit()
except dbus.exceptions.DBusException:
self.mon.warn(self,'Failed to quit - dbus exception')
return
else:
self.mon.warn(self,'Failed to quit - process not running')
return
# kill the subprocess (omxplayer and omxplayer.bin). Used for tidy up on exit.
def terminate(self,reason):
self.terminate_reason=reason
self.stop()
def get_terminate_reason(self):
return self.terminate_reason
# test of whether _process is running
def is_running(self):
retcode=self._process.poll()
# print 'is alive', retcode
if retcode is None:
return True
else:
return False
# kill off omxplayer when it hasn't terminated at the end of a track.
# send SIGINT (CTRL C) so it has a chance to tidy up daemons and omxplayer.bin
def kill(self):
if self.is_running():
self._process.send_signal(signal.SIGINT)
def get_position(self):
"""Return the current position"""
if self.is_running():
try:
micros = self.__iface_props.Position()
return True,micros
except dbus.exceptions.DBusException:
return False,-1
else:
return False,-1
def get_duration(self):
"""Return the total length of the playing media"""
if self.is_running():
try:
micros = self.__iface_props.Duration()
return True,micros
except dbus.exceptions.DBusException:
return False,-1
else:
return False,-1
# *********************
# connect to dbus
# *********************
def __dbus_connect(self):
if self.omx_loaded is False:
# read the omxplayer dbus data from files generated by omxplayer
bus_address_filename = "/tmp/omxplayerdbus.{}".format(self.dbus_user)
bus_pid_filename = "/tmp/omxplayerdbus.{}.pid".format(self.dbus_user)
if not os.path.exists(bus_address_filename):
self.mon.log(self, 'waiting for bus address file ' + bus_address_filename)
self.omx_loaded=False
return False
else:
f = open(bus_address_filename, "r")
bus_address = f.read().rstrip()
if bus_address == '':
self.mon.log(self, 'waiting for bus address in file ' + bus_address_filename)
self.omx_loaded=False
return False
else:
# self.mon.log(self, 'bus address found ' + bus_address)
if not os.path.exists(bus_pid_filename):
self.mon.warn(self, 'bus pid file does not exist ' + bus_pid_filename)
self.omx_loaded=False
return False
else:
f= open(bus_pid_filename, "r")
bus_pid = f.read().rstrip()
if bus_pid == '':
self.omx_loaded=False
return False
else:
# self.mon.log(self, 'bus pid found ' + bus_pid)
os.environ["DBUS_SESSION_BUS_ADDRESS"] = bus_address
os.environ["DBUS_SESSION_BUS_PID"] = bus_pid
self.omx_loaded = True
if self.omx_loaded is True:
session_bus = dbus.SessionBus()
try:
omx_object = session_bus.get_object(self.dbus_name, "/org/mpris/MediaPlayer2", introspect=False)
self.__iface_root = dbus.Interface(omx_object, "org.mpris.MediaPlayer2")
self.__iface_props = dbus.Interface(omx_object, "org.freedesktop.DBus.Properties")
self.__iface_player = dbus.Interface(omx_object, "org.mpris.MediaPlayer2.Player")
except dbus.exceptions.DBusException as ex:
# self.mon.log(self,"Waiting for dbus connection to omxplayer: {}".format(ex.get_dbus_message()))
return False
return True