player: and of course, import os
[hls-player:hls-player.git] / HLS / player.py
1 #!/usr/bin/env python
2 # -*- Mode: Python -*-
3 # vi:si:et:sw=4:sts=4:ts=4
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>
9
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.
16
17 import sys
18 import urlparse
19 import optparse
20 import logging
21 import os
22
23 import pygtk, gtk, gobject
24 gobject.threads_init()
25
26 from twisted.internet import gtk2reactor
27 gtk2reactor.install()
28 from twisted.internet import reactor
29
30 from HLS import __version__
31 from HLS.fetcher import HLSFetcher
32 from HLS.m3u8 import M3U8
33
34 if sys.version_info < (2, 4):
35     raise ImportError("Cannot run with Python version < 2.4")
36
37
38 class HLSControler:
39
40     def __init__(self, fetcher=None):
41         self.fetcher = fetcher
42         self.player = None
43
44         self._player_sequence = None
45         self._n_segments_keep = None
46
47     def set_player(self, player):
48         self.player = player
49         if 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
53
54     def _start(self, first_file):
55         (path, l, f) = first_file
56         self._player_sequence = f['sequence']
57         if self.player:
58             self.player.set_uri(path)
59             self.player.play()
60
61     def start(self):
62         d = self.fetcher.start()
63         d.addCallback(self._start)
64
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)
73
74     def on_player_about_to_finish(self):
75         reactor.callFromThread(self._set_next_uri)
76
77
78 class GSTPlayer:
79
80     def __init__(self, display=True):
81         import pygst
82         import gst
83         if display:
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()
92
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)
98         if display:
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)
103         else:
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
115         self._cb = None
116
117     def need_data(self):
118         return self._need_data
119
120     def play(self):
121         import gst
122         self.player.set_state(gst.STATE_PLAYING)
123         self._playing = True
124
125     def stop(self):
126         import gst
127         self.player.set_state(gst.STATE_NULL)
128         self._playing = False
129
130     def set_uri(self, filepath):
131         import gst
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")
135         if queue0:
136             queue0.set_property("max-size-bytes", 100000)
137         f = open(filepath)
138         self.appsrc.emit('push-buffer', gst.Buffer(f.read()))
139
140     def on_message(self, bus, message):
141         import gst
142         t = message.type
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()
152
153     def on_sync_message(self, bus, message):
154         logging.debug("GstMessage: %r" % (message,))
155         if message.structure is None:
156             return
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()
165
166     def on_decoded_pad(self, decodebin, pad, more_pad):
167         import gst
168         c = pad.get_caps().to_string()
169         if "video" in c:
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")
177             else:
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")
184             pad.link(sink_pad)
185         elif "audio" in c:
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")
194             else:
195                 audiosink = gst.element_factory_make("autoaudiosink", "audiosink")
196
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")
202             pad.link(sink_pad)
203
204     def on_enough_data(self):
205         logging.info("Player is full up!");
206         self._need_data = False;
207
208     def on_need_data(self, src, length):
209         self._need_data = True;
210         self._on_about_to_finish()
211
212     def _on_about_to_finish(self, p=None):
213         if self._cb:
214             self._cb()
215
216     def connect_about_to_finish(self, cb):
217         self._cb = cb
218
219
220 def main():
221     gtk.gdk.threads_init()
222
223     parser = optparse.OptionParser(usage='%prog [options] url...',
224                                    version="%prog " + __version__)
225
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)')
250
251     options, args = parser.parse_args()
252
253     if len(args) == 0:
254         parser.print_help()
255         sys.exit(1)
256
257     if options.verbose:
258         logging.basicConfig(level=logging.DEBUG,
259                             format='%(asctime)s %(levelname)-8s %(message)s',
260                             datefmt='%d %b %Y %H:%M:%S')
261
262     for url in args:
263         for l in range(options.n):
264
265             if urlparse.urlsplit(url).scheme == '':
266                 url = "http://" + url
267
268             c = HLSControler(HLSFetcher(url, options))
269             if not options.nodisplay:
270                 p = GSTPlayer(display = not options.save)
271                 c.set_player(p)
272
273             c.start()
274
275     reactor.run()
276
277
278 if __name__ == '__main__':
279     sys.exit(main())