1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 from twisted.web.resource import Resource
19 from twisted.web.static import Data
20
21 from flumotion.common import log
22 from flumotion.common.errors import ComponentStartError
23 from flumotion.component.misc.httpserver.httpserver import HTTPFileStreamer
24 from flumotion.component.plugs.base import ComponentPlug
25
26 __version__ = "$Rev$"
27
28
29 HEADER = "#EXTM3U"
30
31 ENTRY_TEMPLATE = \
32 """
33 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%(bitrate)d
34 %(stream-url)s"""
35
36 CONTENT_TYPE = "application/vnd.apple.mpegurl"
37 IPAD_TARGET = 600000
38 IPHONE_TARGET = 200000
39 AUDIO_TARGET = 96000
40
41
43 """I am a resource for m3u8 playlists, rendering multibitrate playlists
44 based on the user-agent, so that IPad clients gets a variant playlist in
45 which the first element correspond to the higher bitrate, whilst IPhone
46 ones receive a playlist where the first element has a lower bitrate.
47 """
48
50 Resource.__init__(self)
51
52 self._entries = entries
53 self._target_bitrate = target_bitrate
54 self._min_bitrate = min_bitrate
55
57 playlist = [HEADER]
58
59
60 agent = request.getHeader('User-Agent')
61 if agent and not self._target_bitrate:
62 if 'ipad' in agent.lower():
63 self._target_bitrate = IPAD_TARGET
64 else:
65 self._target_bitrate = IPHONE_TARGET
66
67
68 self._entries.sort(key=lambda x: abs(x['bitrate'] -
69 self._target_bitrate))
70
71
72
73 if self._entries[0]['bitrate'] < self._min_bitrate:
74 self._entries.append(self._entries.pop(0))
75
76 for entry in self._entries:
77 playlist.append(ENTRY_TEMPLATE % entry)
78
79 request.setHeader('Content-type', CONTENT_TYPE)
80 return "\n".join(playlist)
81
82
84 """I am a component plug for a http-server which plugs in a
85 http resource containing a HLS variant playlist.
86 """
87
88 - def start(self, component):
89 """
90 @type component: L{HTTPFileStreamer}
91 """
92 if not isinstance(component, HTTPFileStreamer):
93 raise ComponentStartError(
94 "A MultibitratePlug %s must be plugged into a "
95 " HTTPFileStreamer component, not a %s" % (
96 self, component.__class__.__name__))
97 log.debug('multibitrate', 'Attaching to %r' % (component, ))
98
99 props = self.args['properties']
100 resource = Resource()
101 playlist = PlaylistResource(props.get('playlist-entry', []),
102 props.get('target-bitrate', None))
103
104 resource.putChild(props.get('playlist-name', 'main.m3u8'), playlist)
105 component.setRootResource(resource)
106
107
109 import sys
110 from twisted.internet import reactor
111 from twisted.python.log import startLogging
112 from twisted.web.server import Site
113 startLogging(sys.stderr)
114
115 properties = {
116 'playlist-entry': [
117 {'stream-url':
118 'http://example.com/iphone/low/stream.m3u8',
119 'bitrate': 100000},
120 {'stream-url':
121 'http://example.com/iphone/medium/stream.m3u8',
122 'bitrate': 200000},
123 {'stream-url':
124 'http://example.com/iphone/high/stream.m3u8',
125 'bitrate': 400000},
126 ]}
127
128 root = Resource()
129 mount_point = Resource()
130 playlist = PlaylistResource(properties['playlist-entry'])
131 root.putChild('test', mount_point)
132 mount_point.putChild('main.m3u8', playlist)
133 site = Site(root)
134
135 reactor.listenTCP(8080, site)
136 reactor.run()
137
138 if __name__ == "__main__":
139 test()
140