/
HACKING
174 lines (148 loc) · 7.99 KB
/
HACKING
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
Dwite is a server that takes care of interaction with a SqueezeBox of the
"classic" generation (i.e. SqueezeBox2 is supported, but not SqueezeBox1).
Conman is a content manager that knows how to stream tracks to the device and
performs directory functions on a media library (currently only simple file
system browsing). Dwite and the Conman together form a fully usable system
that can be used to play MP3 and FLAC files (OGG support is coming).
Finally a CLI tool is included to demonstrate the RPC wrappers around core
Dwite functionality.
Dwite is written in Python simply because it is such a lovely language. The
intention is to keep it small enough that it can serve as a reference when
rewriting in C, if necessary. Such a rewrite would not appear to be needed
for performance reasons unless you expect it to run well on a 100MHz machine
of the early 90's or something like that. Then again, that might be exactly
the point...
Dependencies
============
* PIL - To render bitmaps from TTF fonts and things like that.
* Mutagen - To extract media file meta data.
* libmagic + magic Python wrappers.
* libflac + pyflac Python wrappers (https://github.com/dsully/pyflac).
* SQLite3.
The code is not terribly hard to port to Python 3, but several dependencies
do not support Python 3, so it would still be a lot of work to make it happen.
Class & module hierarchies
==========================
dwite / dwite.py
----------------
Bootstrap. Controls whole system liveness. (Note that content managers
must be started separately.)
wire.py / protocol.py
---------------------
The SlimWire class handles all sending and receiving of SlimProto messages
over the wire.
The JsonWire class handles all communication with content managers.
The protocol classes are split into Commands and Messages, where messages
can come from a device or a content manager. Commands are used to tell a
device what to do.
Device
------
The topmost container that holds all state pertaining to a physical device.
Some state is left out and must be specified in subclasses. There is, for
instance, no "display" member because some models don't have one. A Device
is essentially the main loop of the program. All that goes on in dwite.py
is just a bootstrap to get everything up and running.
Classic(Device)
---------------
Subclass of Device that handles the "classic" model, including the "SB2" and
"SB3" models previously sold by SlimDevices. All display updates are driven
by a ticker that wakes up 50 times per second. When it does, a display render
will be chosen based on the current state of the device and its .ticker()
function will be called. This allows for relatively easy construction of
rendering logic that is well contained and can do pretty much whatever it
wants with the display. There is never more than one render active at any
time, but new renders can be created that combine functionality from existing
ones to produce complex visual effects.
tactile.py
----------
Currently only contains IR code definitions for the SB2/SB3 remote control. I
chose the name "tactile" rather than "remote" because e.g. the Trasporter has
a bunch of buttons on the device itself. I.e. tactile but not remote buttons.
Such buttons should eventually be mapped up in here as well.
Display
-------
A container class that represents the box's display. It knows how to send
bitmaps to a physical display and handles intrinsics such as brightness and
transition styles (i.e. scroll and bump). Visualization control will go in
here at some point. Graphics that require host-side rendering don't belong
in this class.
Menu
----
Uses a set of classes that represent various kinds of tree nodes. The topmost
tree class just contains a label (to show on a Display) and a reference
"upwards" to a parent node.
Subclasses of the Tree class are used to represent file system directories,
info about media files, screen savers and all kinds of stuff. Container
classes must implement the ls() method.
Given a bunch of container and leaf nodes, the Menu can browse the content by
entering & leaving containers, et.c. The currently displayed entry is tracked
with an index against its container's array of children. When a container is
left, the label of the container is searched for in the parent node to find
the new index value.
Tree nodes are expected to know which render object to use when visualizing
themselves.
The most important class member is perhaps tick(), which has to be called
periodically to drive any render pipelines that Menu nodes have set up.
IMPORTANT DESIGN DECISION: Much of the functionality that a user can interact
with is implemented as specialized menu entries. Many tactile codes are passed
on to the currently active Menu item to let the item add special handling of
tactile events. There is, for instance, a class Searcher which interprets the
numeric keys on the remote in T9 style and looks up possible completions in a
dictionary. (The dictionary contains any terms that content managers have sent
in.) This way, the user can quickly build a phrase of search terms that can be
passed off to content managers.
Canvas
------
Uses PIL classes to draw and manipulate bitmaps that can be sent to a device.
Its primary purpose is to transform bitmaps to draw correctly on screeen. At
some point it may be interesting to create abstractions for image compositing
in here as well.
Render
------
Timekeeping Canvas user. Must be subclassed. All subclasses must implement
the tick() method. tick() must be called periodically while the render is
active. Look at the TextRender subclass for a reasonably simple example (it
automatically scrolls strings that are too long to fit on the Canvas).
player.py
---------
The Player and NowPlaying classes implement handlers for the playback related
buttons on the remote and keep track of the playback status messages that come
in from a device. They also handle volume control (which is definitely buggy).
conman / conman.py
--------------
Liveness control of a minimalistic content manager that runs as a separate
program. It communicates with Dwite by sending JSON formatted messages over
a TCP socket (using the JsonWire class, discussed earlier). If you want to
build a content manager (such as a SqueezeBox plugin for your favourite media
player), start by looking at the RPC API that Cleo implements.
streamer.py
-----------
Used by the content manager to respond to HTTP GET requests from the device.
Also contains MP3 and FLAC "decoder" classes. The decoder doesn't unpack
the MP3 contents into PCM frames or anything like that; It merely maps time
seeks to file offsets. I.e. if the device asks for a particular track and a
time offset, the streamer will dig up the corresponding file and decode the
file offset to start from. It probably does this incorrectly for variable bit
rate songs.
Basically, a full interaction "cycle" between device manager, device, streamer
and content manager can be characterized like this:
* The content manager answers an Ls request from the device manager.
* The device manager uses the Ls response to create menu entries.
* The user presses play when a menu entry is focused.
* The device manager tells the device to fetch a GUID that corresponds to
the menu item that was focused when the user pressed play. It also tells
the device what kind of content to expect when the streaming starts, at
what address:port the streamer i located, and other little bits. Check the
Strm class in protocol.py.
* The device cooks an HTTP GET request and sends it to the address:port that
the device manager just told it about.
* The streamer receives the request. Because it is built into the content
manager that told the device manager about content identifiers in the first
place, it is able to translate the requested GUID into an actual file to
stream.
* The streamer sends the file, little by little.
* The device manager produces Stat messages during playback and sends them to
the device manager.
* The device manager uses the Stat messages to update the display on the
device. Or it could post them to an IRC channel or what-not.
HAPPY HACKING!