3 # vi:si:et:sw=4:sts=4:ts=4
5 # Copyright (C) 2009-2010 Fluendo, S.L. (www.fluendo.com).
6 # Copyright (C) 2009-2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
7 # Copyright (C) 2010 Zaheer Abbas Merali <zaheerabbas at merali dot org>
8 # Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
10 # This file may be distributed and/or modified under the terms of
11 # the GNU General Public License version 2 as published by
12 # the Free Software Foundation.
13 # This file is distributed without any warranty; without even the implied
14 # warranty of merchantability or fitness for a particular purpose.
15 # See "LICENSE" in the source distribution for more information.
23 import pygtk, gtk, gobject
24 gobject.threads_init()
26 from twisted.internet import gtk2reactor
28 from twisted.internet import reactor
30 from HLS import __version__
31 from HLS.fetcher import HLSFetcher
32 from HLS.m3u8 import M3U8
34 if sys.version_info < (2, 4):
35 raise ImportError("Cannot run with Python version < 2.4")
40 def __init__(self, fetcher=None):
41 self.fetcher = fetcher
44 self._player_sequence = None
45 self._n_segments_keep = None
47 def set_player(self, player):
50 self.player.connect_about_to_finish(self.on_player_about_to_finish)
51 self._n_segments_keep = self.fetcher.n_segments_keep
52 self.fetcher.n_segments_keep = -1
54 def _start(self, first_file):
55 (path, l, f) = first_file
56 self._player_sequence = f['sequence']
58 self.player.set_uri(path)
62 d = self.fetcher.start()
63 d.addCallback(self._start)
65 def _set_next_uri(self):
66 # keep only the past three segments
67 if self._n_segments_keep != -1:
68 self.fetcher.delete_cache(lambda x:
69 x <= self._player_sequence - self._n_segments_keep)
70 self._player_sequence += 1
71 d = self.fetcher.get_file(self._player_sequence)
72 d.addCallback(self.player.set_uri)
74 def on_player_about_to_finish(self):
75 reactor.callFromThread(self._set_next_uri)
80 def __init__(self, display=True):
84 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
85 self.window.set_title("Video-Player")
86 self.window.set_default_size(500, 400)
87 self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
88 self.window.connect('delete-event', lambda _: reactor.stop())
89 self.movie_window = gtk.DrawingArea()
90 self.window.add(self.movie_window)
91 self.window.show_all()
93 self.player = gst.Pipeline("player")
94 self.appsrc = gst.element_factory_make("appsrc", "source")
95 self.appsrc.connect("enough-data", self.on_enough_data)
96 self.appsrc.connect("need-data", self.on_need_data)
97 self.appsrc.set_property("max-bytes", 10000)
99 self.decodebin = gst.element_factory_make("decodebin2", "decodebin")
100 self.decodebin.connect("new-decoded-pad", self.on_decoded_pad)
101 self.player.add(self.appsrc, self.decodebin)
102 gst.element_link_many(self.appsrc, self.decodebin)
104 sink = gst.element_factory_make("filesink", "filesink")
105 sink.set_property("location", "/tmp/hls-player.ts")
106 self.player.add(self.appsrc, sink)
107 gst.element_link_many(self.appsrc, sink)
108 bus = self.player.get_bus()
109 bus.add_signal_watch()
110 bus.enable_sync_message_emission()
111 bus.connect("message", self.on_message)
112 bus.connect("sync-message::element", self.on_sync_message)
113 self._playing = False
114 self._need_data = False
118 return self._need_data
122 self.player.set_state(gst.STATE_PLAYING)
127 self.player.set_state(gst.STATE_NULL)
128 self._playing = False
130 def set_uri(self, filepath):
132 logging.debug("Pushing %r to appsrc" % filepath)
133 # FIXME: BIG hack to reduce the initial starting time...
134 queue0 = self.decodebin.get_by_name("multiqueue0")
136 queue0.set_property("max-size-bytes", 100000)
138 self.appsrc.emit('push-buffer', gst.Buffer(f.read()))
140 def on_message(self, bus, message):
143 if t == gst.MESSAGE_EOS:
144 self.player.set_state(gst.STATE_NULL)
145 elif t == gst.MESSAGE_ERROR:
146 self.player.set_state(gst.STATE_NULL)
147 err, debug = message.parse_error()
148 print "Error: %s" % err, debug
149 elif t == gst.MESSAGE_STATE_CHANGED:
150 if message.src == self.player:
151 o, n, p = message.parse_state_changed()
153 def on_sync_message(self, bus, message):
154 logging.debug("GstMessage: %r" % (message,))
155 if message.structure is None:
157 message_name = message.structure.get_name()
158 if message_name == "prepare-xwindow-id":
159 imagesink = message.src
160 gtk.gdk.threads_enter()
161 gtk.gdk.display_get_default().sync()
162 imagesink.set_property("force-aspect-ratio", True)
163 imagesink.set_xwindow_id(self.movie_window.window.xid)
164 gtk.gdk.threads_leave()
166 def on_decoded_pad(self, decodebin, pad, more_pad):
168 c = pad.get_caps().to_string()
170 q1 = gst.element_factory_make("queue", "vqueue")
171 q1.props.max_size_buffers = 0
172 q1.props.max_size_time = 0
173 #q1.props.max_size_bytes = 0
174 colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace")
175 if 'HLS_VIDEOSINK' in os.environ.keys():
176 videosink = gst.element_factory_make(os.environ['HLS_VIDEOSINK'], "videosink")
178 videosink = gst.element_factory_make("xvimagesink", "videosink")
179 self.player.add(q1, colorspace, videosink)
180 gst.element_link_many(q1, colorspace, videosink)
181 for e in [q1, colorspace, videosink]:
182 e.set_state(gst.STATE_PLAYING)
183 sink_pad = q1.get_pad("sink")
186 q2 = gst.element_factory_make("queue", "aqueue")
187 q2.props.max_size_buffers = 0
188 q2.props.max_size_time = 0
189 #q2.props.max_size_bytes = 0
190 audioconv = gst.element_factory_make("audioconvert", "audioconv")
191 audioresample = gst.element_factory_make("audioresample", "ar")
192 if 'HLS_AUDIOSINK' in os.environ.keys():
193 audiosink = gst.element_factory_make(os.environ['HLS_AUDIOSINK'], "audiosink")
195 audiosink = gst.element_factory_make("autoaudiosink", "audiosink")
197 self.player.add(q2, audioconv, audioresample, audiosink)
198 gst.element_link_many(q2, audioconv, audioresample, audiosink)
199 for e in [q2, audioconv, audioresample, audiosink]:
200 e.set_state(gst.STATE_PLAYING)
201 sink_pad = q2.get_pad("sink")
204 def on_enough_data(self):
205 logging.info("Player is full up!");
206 self._need_data = False;
208 def on_need_data(self, src, length):
209 self._need_data = True;
210 self._on_about_to_finish()
212 def _on_about_to_finish(self, p=None):
216 def connect_about_to_finish(self, cb):
221 gtk.gdk.threads_init()
223 parser = optparse.OptionParser(usage='%prog [options] url...',
224 version="%prog " + __version__)
226 parser.add_option('-v', '--verbose', action="store_true",
227 dest='verbose', default=False,
228 help='print some debugging (default: %default)')
229 parser.add_option('-b', '--bitrate', action="store",
230 dest='bitrate', default=200000, type="int",
231 help='desired bitrate (default: %default)')
232 parser.add_option('-k', '--keep', action="store",
233 dest='keep', default=3, type="int",
234 help='number of segments ot keep (default: %default, -1: unlimited)')
235 parser.add_option('-r', '--referer', action="store", metavar="URL",
236 dest='referer', default=None,
237 help='Sends the "Referer Page" information with URL')
238 parser.add_option('-D', '--no-display', action="store_true",
239 dest='nodisplay', default=False,
240 help='display no video (default: %default)')
241 parser.add_option('-s', '--save', action="store_true",
242 dest='save', default=False,
243 help='save instead of watch (saves to /tmp/hls-player.ts)')
244 parser.add_option('-p', '--path', action="store", metavar="PATH",
245 dest='path', default=None,
246 help='download files to PATH')
247 parser.add_option('-n', '--number', action="store",
248 dest='n', default=1, type="int",
249 help='number of player to start (default: %default)')
251 options, args = parser.parse_args()
258 logging.basicConfig(level=logging.DEBUG,
259 format='%(asctime)s %(levelname)-8s %(message)s',
260 datefmt='%d %b %Y %H:%M:%S')
263 for l in range(options.n):
265 if urlparse.urlsplit(url).scheme == '':
266 url = "http://" + url
268 c = HLSControler(HLSFetcher(url, options))
269 if not options.nodisplay:
270 p = GSTPlayer(display = not options.save)
278 if __name__ == '__main__':