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
29 from twisted.python import log
31 from HLS import __version__
32 from HLS.fetcher import HLSFetcher
33 from HLS.m3u8 import M3U8
35 if sys.version_info < (2, 4):
36 raise ImportError("Cannot run with Python version < 2.4")
41 def __init__(self, fetcher=None):
42 self.fetcher = fetcher
45 self._player_sequence = None
46 self._n_segments_keep = None
48 def set_player(self, player):
51 self.player.connect_about_to_finish(self.on_player_about_to_finish)
52 self._n_segments_keep = self.fetcher.n_segments_keep
53 self.fetcher.n_segments_keep = -1
55 def _start(self, first_file):
56 (path, l, f) = first_file
57 self._player_sequence = f['sequence']
59 self.player.set_uri(path)
63 d = self.fetcher.start()
64 d.addCallback(self._start)
66 def _set_next_uri(self):
67 # keep only the past three segments
68 if self._n_segments_keep != -1:
69 self.fetcher.delete_cache(lambda x:
70 x <= self._player_sequence - self._n_segments_keep)
71 self._player_sequence += 1
72 d = self.fetcher.get_file(self._player_sequence)
73 d.addCallback(self.player.set_uri)
75 def on_player_about_to_finish(self):
76 reactor.callFromThread(self._set_next_uri)
81 def __init__(self, display=True):
85 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
86 self.window.set_title("Video-Player")
87 self.window.set_default_size(500, 400)
88 self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
89 self.window.connect('delete-event', lambda _: reactor.stop())
90 self.movie_window = gtk.DrawingArea()
91 self.window.add(self.movie_window)
92 self.window.show_all()
94 self.player = gst.Pipeline("player")
95 self.appsrc = gst.element_factory_make("appsrc", "source")
96 self.appsrc.connect("enough-data", self.on_enough_data)
97 self.appsrc.connect("need-data", self.on_need_data)
98 self.appsrc.set_property("max-bytes", 10000)
100 self.decodebin = gst.element_factory_make("decodebin2", "decodebin")
101 self.decodebin.connect("new-decoded-pad", self.on_decoded_pad)
102 self.player.add(self.appsrc, self.decodebin)
103 gst.element_link_many(self.appsrc, self.decodebin)
105 sink = gst.element_factory_make("filesink", "filesink")
106 sink.set_property("location", "/tmp/hls-player.ts")
107 self.player.add(self.appsrc, sink)
108 gst.element_link_many(self.appsrc, sink)
109 bus = self.player.get_bus()
110 bus.add_signal_watch()
111 bus.enable_sync_message_emission()
112 bus.connect("message", self.on_message)
113 bus.connect("sync-message::element", self.on_sync_message)
114 self._playing = False
115 self._need_data = False
119 return self._need_data
123 self.player.set_state(gst.STATE_PLAYING)
128 self.player.set_state(gst.STATE_NULL)
129 self._playing = False
131 def set_uri(self, filepath):
133 logging.debug("Pushing %r to appsrc" % filepath)
134 # FIXME: BIG hack to reduce the initial starting time...
135 queue0 = self.decodebin.get_by_name("multiqueue0")
137 queue0.set_property("max-size-bytes", 100000)
139 self.appsrc.emit('push-buffer', gst.Buffer(f.read()))
141 def on_message(self, bus, message):
144 if t == gst.MESSAGE_EOS:
145 self.player.set_state(gst.STATE_NULL)
146 elif t == gst.MESSAGE_ERROR:
147 self.player.set_state(gst.STATE_NULL)
148 err, debug = message.parse_error()
149 print "Error: %s" % err, debug
150 elif t == gst.MESSAGE_STATE_CHANGED:
151 if message.src == self.player:
152 o, n, p = message.parse_state_changed()
154 def on_sync_message(self, bus, message):
155 logging.debug("GstMessage: %r" % (message,))
156 if message.structure is None:
158 message_name = message.structure.get_name()
159 if message_name == "prepare-xwindow-id":
160 imagesink = message.src
161 gtk.gdk.threads_enter()
162 gtk.gdk.display_get_default().sync()
163 imagesink.set_property("force-aspect-ratio", True)
164 imagesink.set_xwindow_id(self.movie_window.window.xid)
165 gtk.gdk.threads_leave()
167 def on_decoded_pad(self, decodebin, pad, more_pad):
169 c = pad.get_caps().to_string()
171 q1 = gst.element_factory_make("queue", "vqueue")
172 q1.props.max_size_buffers = 0
173 q1.props.max_size_time = 0
174 #q1.props.max_size_bytes = 0
175 colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace")
176 if 'HLS_VIDEOSINK' in os.environ.keys():
177 videosink = gst.element_factory_make(os.environ['HLS_VIDEOSINK'], "videosink")
179 videosink = gst.element_factory_make("xvimagesink", "videosink")
180 self.player.add(q1, colorspace, videosink)
181 gst.element_link_many(q1, colorspace, videosink)
182 for e in [q1, colorspace, videosink]:
183 e.set_state(gst.STATE_PLAYING)
184 sink_pad = q1.get_pad("sink")
187 q2 = gst.element_factory_make("queue", "aqueue")
188 q2.props.max_size_buffers = 0
189 q2.props.max_size_time = 0
190 #q2.props.max_size_bytes = 0
191 audioconv = gst.element_factory_make("audioconvert", "audioconv")
192 audioresample = gst.element_factory_make("audioresample", "ar")
193 if 'HLS_AUDIOSINK' in os.environ.keys():
194 audiosink = gst.element_factory_make(os.environ['HLS_AUDIOSINK'], "audiosink")
196 audiosink = gst.element_factory_make("autoaudiosink", "audiosink")
198 self.player.add(q2, audioconv, audioresample, audiosink)
199 gst.element_link_many(q2, audioconv, audioresample, audiosink)
200 for e in [q2, audioconv, audioresample, audiosink]:
201 e.set_state(gst.STATE_PLAYING)
202 sink_pad = q2.get_pad("sink")
205 def on_enough_data(self):
206 logging.info("Player is full up!");
207 self._need_data = False;
209 def on_need_data(self, src, length):
210 self._need_data = True;
211 self._on_about_to_finish()
213 def _on_about_to_finish(self, p=None):
217 def connect_about_to_finish(self, cb):
222 gtk.gdk.threads_init()
224 parser = optparse.OptionParser(usage='%prog [options] url...',
225 version="%prog " + __version__)
227 parser.add_option('-v', '--verbose', action="store_true",
228 dest='verbose', default=False,
229 help='print some debugging (default: %default)')
230 parser.add_option('-b', '--bitrate', action="store",
231 dest='bitrate', default=200000, type="int",
232 help='desired bitrate (default: %default)')
233 parser.add_option('-u', '--buffer', action="store", metavar="N",
234 dest='buffer', default=3, type="int",
235 help='pre-buffer N segments at start')
236 parser.add_option('-k', '--keep', action="store",
237 dest='keep', default=3, type="int",
238 help='number of segments ot keep (default: %default, -1: unlimited)')
239 parser.add_option('-r', '--referer', action="store", metavar="URL",
240 dest='referer', default=None,
241 help='Sends the "Referer Page" information with URL')
242 parser.add_option('-D', '--no-display', action="store_true",
243 dest='nodisplay', default=False,
244 help='display no video (default: %default)')
245 parser.add_option('-s', '--save', action="store_true",
246 dest='save', default=False,
247 help='save instead of watch (saves to /tmp/hls-player.ts)')
248 parser.add_option('-p', '--path', action="store", metavar="PATH",
249 dest='path', default=None,
250 help='download files to PATH')
251 parser.add_option('-n', '--number', action="store",
252 dest='n', default=1, type="int",
253 help='number of player to start (default: %default)')
255 options, args = parser.parse_args()
261 log.PythonLoggingObserver().start()
263 logging.basicConfig(level=logging.DEBUG,
264 format='%(asctime)s %(levelname)-8s %(message)s',
265 datefmt='%d %b %Y %H:%M:%S')
269 for l in range(options.n):
271 if urlparse.urlsplit(url).scheme == '':
272 url = "http://" + url
274 c = HLSControler(HLSFetcher(url, options))
275 if not options.nodisplay:
276 p = GSTPlayer(display = not options.save)
279 delay = 10.0 / options.n * n
281 reactor.callLater(delay, c.start)
286 if __name__ == '__main__':