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.
22 import pygtk, gtk, gobject
23 gobject.threads_init()
25 from twisted.internet import gtk2reactor
27 from twisted.internet import reactor
29 from HLS import __version__
30 from HLS.fetcher import HLSFetcher
31 from HLS.m3u8 import M3U8
33 if sys.version_info < (2, 4):
34 raise ImportError("Cannot run with Python version < 2.4")
39 def __init__(self, fetcher=None):
40 self.fetcher = fetcher
43 self._player_sequence = None
44 self._n_segments_keep = None
46 def set_player(self, player):
49 self.player.connect_about_to_finish(self.on_player_about_to_finish)
50 self._n_segments_keep = self.fetcher.n_segments_keep
51 self.fetcher.n_segments_keep = -1
53 def _start(self, first_file):
54 (path, l, f) = first_file
55 self._player_sequence = f['sequence']
57 self.player.set_uri(path)
61 d = self.fetcher.start()
62 d.addCallback(self._start)
64 def _set_next_uri(self):
65 # keep only the past three segments
66 if self._n_segments_keep != -1:
67 self.fetcher.delete_cache(lambda x:
68 x <= self._player_sequence - self._n_segments_keep)
69 self._player_sequence += 1
70 d = self.fetcher.get_file(self._player_sequence)
71 d.addCallback(self.player.set_uri)
73 def on_player_about_to_finish(self):
74 reactor.callFromThread(self._set_next_uri)
79 def __init__(self, display=True):
83 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
84 self.window.set_title("Video-Player")
85 self.window.set_default_size(500, 400)
86 self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
87 self.window.connect('delete-event', lambda _: reactor.stop())
88 self.movie_window = gtk.DrawingArea()
89 self.window.add(self.movie_window)
90 self.window.show_all()
92 self.player = gst.Pipeline("player")
93 self.appsrc = gst.element_factory_make("appsrc", "source")
94 self.appsrc.connect("enough-data", self.on_enough_data)
95 self.appsrc.connect("need-data", self.on_need_data)
96 self.appsrc.set_property("max-bytes", 10000)
98 self.decodebin = gst.element_factory_make("decodebin2", "decodebin")
99 self.decodebin.connect("new-decoded-pad", self.on_decoded_pad)
100 self.player.add(self.appsrc, self.decodebin)
101 gst.element_link_many(self.appsrc, self.decodebin)
103 sink = gst.element_factory_make("filesink", "filesink")
104 sink.set_property("location", "/tmp/hls-player.ts")
105 self.player.add(self.appsrc, sink)
106 gst.element_link_many(self.appsrc, sink)
107 bus = self.player.get_bus()
108 bus.add_signal_watch()
109 bus.enable_sync_message_emission()
110 bus.connect("message", self.on_message)
111 bus.connect("sync-message::element", self.on_sync_message)
112 self._playing = False
113 self._need_data = False
117 return self._need_data
121 self.player.set_state(gst.STATE_PLAYING)
126 self.player.set_state(gst.STATE_NULL)
127 self._playing = False
129 def set_uri(self, filepath):
131 logging.debug("Pushing %r to appsrc" % filepath)
132 # FIXME: BIG hack to reduce the initial starting time...
133 queue0 = self.decodebin.get_by_name("multiqueue0")
135 queue0.set_property("max-size-bytes", 100000)
137 self.appsrc.emit('push-buffer', gst.Buffer(f.read()))
139 def on_message(self, bus, message):
142 if t == gst.MESSAGE_EOS:
143 self.player.set_state(gst.STATE_NULL)
144 elif t == gst.MESSAGE_ERROR:
145 self.player.set_state(gst.STATE_NULL)
146 err, debug = message.parse_error()
147 print "Error: %s" % err, debug
148 elif t == gst.MESSAGE_STATE_CHANGED:
149 if message.src == self.player:
150 o, n, p = message.parse_state_changed()
152 def on_sync_message(self, bus, message):
153 logging.debug("GstMessage: %r" % (message,))
154 if message.structure is None:
156 message_name = message.structure.get_name()
157 if message_name == "prepare-xwindow-id":
158 imagesink = message.src
159 gtk.gdk.threads_enter()
160 gtk.gdk.display_get_default().sync()
161 imagesink.set_property("force-aspect-ratio", True)
162 imagesink.set_xwindow_id(self.movie_window.window.xid)
163 gtk.gdk.threads_leave()
165 def on_decoded_pad(self, decodebin, pad, more_pad):
167 c = pad.get_caps().to_string()
169 q1 = gst.element_factory_make("queue", "vqueue")
170 q1.props.max_size_buffers = 0
171 q1.props.max_size_time = 0
172 #q1.props.max_size_bytes = 0
173 colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace")
174 videosink = gst.element_factory_make("xvimagesink", "videosink")
175 self.player.add(q1, colorspace, videosink)
176 gst.element_link_many(q1, colorspace, videosink)
177 for e in [q1, colorspace, videosink]:
178 e.set_state(gst.STATE_PLAYING)
179 sink_pad = q1.get_pad("sink")
182 q2 = gst.element_factory_make("queue", "aqueue")
183 q2.props.max_size_buffers = 0
184 q2.props.max_size_time = 0
185 #q2.props.max_size_bytes = 0
186 audioconv = gst.element_factory_make("audioconvert", "audioconv")
187 audioresample = gst.element_factory_make("audioresample", "ar")
188 audiosink = gst.element_factory_make("autoaudiosink", "audiosink")
189 self.player.add(q2, audioconv, audioresample, audiosink)
190 gst.element_link_many(q2, audioconv, audioresample, audiosink)
191 for e in [q2, audioconv, audioresample, audiosink]:
192 e.set_state(gst.STATE_PLAYING)
193 sink_pad = q2.get_pad("sink")
196 def on_enough_data(self):
197 logging.info("Player is full up!");
198 self._need_data = False;
200 def on_need_data(self, src, length):
201 self._need_data = True;
202 self._on_about_to_finish()
204 def _on_about_to_finish(self, p=None):
208 def connect_about_to_finish(self, cb):
213 gtk.gdk.threads_init()
215 parser = optparse.OptionParser(usage='%prog [options] url...',
216 version="%prog " + __version__)
218 parser.add_option('-v', '--verbose', action="store_true",
219 dest='verbose', default=False,
220 help='print some debugging (default: %default)')
221 parser.add_option('-b', '--bitrate', action="store",
222 dest='bitrate', default=200000, type="int",
223 help='desired bitrate (default: %default)')
224 parser.add_option('-k', '--keep', action="store",
225 dest='keep', default=3, type="int",
226 help='number of segments ot keep (default: %default, -1: unlimited)')
227 parser.add_option('-r', '--referer', action="store", metavar="URL",
228 dest='referer', default=None,
229 help='Sends the "Referer Page" information with URL')
230 parser.add_option('-D', '--no-display', action="store_true",
231 dest='nodisplay', default=False,
232 help='display no video (default: %default)')
233 parser.add_option('-s', '--save', action="store_true",
234 dest='save', default=False,
235 help='save instead of watch (saves to /tmp/hls-player.ts)')
236 parser.add_option('-p', '--path', action="store", metavar="PATH",
237 dest='path', default=None,
238 help='download files to PATH')
239 parser.add_option('-n', '--number', action="store",
240 dest='n', default=1, type="int",
241 help='number of player to start (default: %default)')
243 options, args = parser.parse_args()
250 logging.basicConfig(level=logging.DEBUG,
251 format='%(asctime)s %(levelname)-8s %(message)s',
252 datefmt='%d %b %Y %H:%M:%S')
255 for l in range(options.n):
257 if urlparse.urlsplit(url).scheme == '':
258 url = "http://" + url
260 c = HLSControler(HLSFetcher(url, options))
261 if not options.nodisplay:
262 p = GSTPlayer(display = not options.save)
270 if __name__ == '__main__':