-
Notifications
You must be signed in to change notification settings - Fork 4
/
player.py
216 lines (170 loc) · 8.99 KB
/
player.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
import ctypes
import logging
import threading
import vlc
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib
LOG = logging.getLogger('epic_narrator.player')
class Player:
def __init__(self, widget, controller):
LOG.info('Creating VLC player')
self.controller = controller
self.vlc_instance = vlc.Instance('--no-xlib')
self.video_player = self.vlc_instance.media_player_new()
self.rec_player = self.vlc_instance.media_player_new()
self.video_length = 0
self.set_vlc_window(widget, controller.this_os)
self.mute_video()
self._seeking_timeout = 0
self.was_playing_before_seek = None
self._is_seeking = False
self.is_dragging = False
self.seek_refresh = 50 # milliseconds
self.seek_step = 500 # milliseconds
# from lib vlc documentation. Make sure you don't use wait anywhere in the program
'''
while LibVLC is active, the wait() function shall not be called, and
any call to waitpid() shall use a strictly positive value for the first
parameter (i.e. the PID). Failure to follow those rules may lead to a
deadlock or a busy loop.
'''
# VERY IMPORTANT: functions attached to vlc events will be run in a separate thread,
# VLC is not thread safe, so if you call any method that will access the vlc player from this dummy threads
# you will get seg faults randomly
# Use glib.idle_add() to invoke things from the main thread
main_events = self.video_player.event_manager()
main_events.event_attach(vlc.EventType.MediaPlayerPositionChanged, self.video_moving_handler)
main_events.event_attach(vlc.EventType.MediaPlayerEndReached, self.video_ended_handler)
main_events.event_attach(vlc.EventType.MediaPlayerLengthChanged, self.video_loaded_handler)
rec_events = self.rec_player.event_manager()
rec_events.event_attach(vlc.EventType.MediaPlayerStopped, self.finished_playing_recording_handler)
def set_vlc_window(self, widget, this_os):
LOG.info('Setting up vlc window (os={})'.format(this_os))
if this_os == 'linux':
win_id = widget.get_window().get_xid()
self.video_player.set_xwindow(win_id)
elif this_os == 'mac_os':
# ugly bit to get window if on mac os
window = widget.get_property('window')
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(window.__gpointer__, None)
libgdk = ctypes.CDLL("libgdk-3.dylib")
libgdk.gdk_quartz_window_get_nsview.restype = ctypes.c_void_p
libgdk.gdk_quartz_window_get_nsview.argtypes = [ctypes.c_void_p]
handle = libgdk.gdk_quartz_window_get_nsview(gpointer)
self.video_player.set_nsobject(int(handle))
elif this_os == 'windows':
window = widget.get_property('window')
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
drawingarea_gpointer = ctypes.pythonapi.PyCapsule_GetPointer(window.__gpointer__, None)
gdkdll = ctypes.CDLL("libgdk-3-0.dll")
handle = gdkdll.gdk_win32_window_get_handle(drawingarea_gpointer)
self.video_player.set_hwnd(int(handle))
else:
LOG.error('Cannot deal with this platform: {}'.format(this_os))
raise Exception('Cannot deal with this platform: {}'.format(this_os))
def shutting_down(self):
LOG.info('Releasing vlc instance (thread={})'.format(threading.current_thread().getName()))
self.video_player.stop()
self.rec_player.stop()
self.vlc_instance.release()
def load_video(self, video_path):
LOG.info('Loading video {} (thread={})'.format(video_path, threading.current_thread().getName()))
media = self.vlc_instance.media_new_path(video_path)
self.video_player.set_mrl(media.get_mrl())
self.play_video() # we need to play the video for a while to get the length in milliseconds
def video_loaded_handler(self, *args):
GLib.idle_add(self.video_loaded)
def video_loaded(self):
LOG.info('Video loaded (thread={})'.format(threading.current_thread().getName()))
self.pause_video()
self.video_length = self.get_video_length()
self.controller.video_loaded()
def get_video_length(self):
LOG.info('Getting video length (thread={})'.format(threading.current_thread().getName()))
return self.video_player.get_length()
def play_video(self):
LOG.info('Playing video (thread={})'.format(threading.current_thread().getName()))
self.video_player.play()
def pause_video(self):
LOG.info('Pausing video (thread={})'.format(threading.current_thread().getName()))
self.video_player.set_pause(True)
def set_speed(self, speed):
LOG.info('Setting playback speed to {} (thread={})'.format(speed, threading.current_thread().getName()))
self.video_player.set_rate(speed)
def mute_video(self):
LOG.info('Mute video (thread={})'.format(threading.current_thread().getName()))
self.video_player.audio_set_mute(True)
def unmute_video(self):
LOG.info('Unmute video (thread={})'.format(threading.current_thread().getName()))
self.video_player.audio_set_mute(False)
def get_current_position(self):
# this is called constantly as the video plays, avoid logging
return max(0, self.video_player.get_time())
def is_playing(self):
LOG.info('Checking if video is playing (thread={})'.format(threading.current_thread().getName()))
return self.video_player.is_playing()
def is_mute(self):
LOG.info('Checking if video is mute (thread={})'.format(threading.current_thread().getName()))
return self.video_player.audio_get_mute()
def is_seeking(self):
# this is called constantly as the video plays, avoid logging
return self._is_seeking or self._seeking_timeout != 0
def video_moving_handler(self, *args):
# this will be run in the main thread when possible
GLib.idle_add(self.video_moving, priority=GLib.PRIORITY_HIGH)
def video_moving(self):
# this is called constantly as the video plays, avoid logging
self.controller.signal_sender.emit('video_moving', self.get_current_position(), self.is_seeking())
def start_seek(self, direction):
LOG.info('Start seeking (thread={})'.format(threading.current_thread().getName()))
if self.video_player.is_playing():
self.pause_video()
self.was_playing_before_seek = True
else:
self.was_playing_before_seek = False
step = self.seek_step if direction == 'forward' else - self.seek_step
self._seeking_timeout = GLib.timeout_add(self.seek_refresh, self.seek, step)
def stop_seek(self):
LOG.info('Stop seeking (thread={})'.format(threading.current_thread().getName()))
GLib.source_remove(self._seeking_timeout)
self._seeking_timeout = 0
if self.was_playing_before_seek:
self.play_video()
self._is_seeking = False
def seek(self, step):
seek_pos = self.get_current_position() + step
if 0 < seek_pos < self.video_length:
self._is_seeking = True
self.video_player.set_time(int(seek_pos))
self.controller.signal_sender.emit('video_moving', self.get_current_position(), self.is_seeking())
# always return True to make sure the event id is kept in glib
return True
def go_to(self, time_ms):
LOG.info('Go to {}ms (thread={})'.format(time_ms, threading.current_thread().getName()))
self.video_player.set_time(int(time_ms))
def video_ended_handler(self, *args):
GLib.idle_add(self.video_ended)
def video_ended(self):
LOG.info('Video ended (thread={})'.format(threading.current_thread().getName()))
self.video_player.stop()
self.controller.reload_current_video()
def play_recording(self, recording_path):
LOG.info('Playing recording at {} (thread={})'.format(recording_path, threading.current_thread().getName()))
audio_media = self.vlc_instance.media_new_path(recording_path)
self.rec_player.audio_set_mute(False) # we need to this every time
self.rec_player.set_mrl(audio_media.get_mrl())
self.rec_player.play()
def finished_playing_recording_handler(self, *args):
GLib.idle_add(self.finished_playing_recording)
def finished_playing_recording(self):
self.controller.recording_finished_playing()
def reset(self):
LOG.info('Resetting (thread={})'.format(threading.current_thread().getName()))
self.video_length = 0
self._seeking_timeout = 0
self.was_playing_before_seek = None
self._is_seeking = False