2006-11-29 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / README.daap
blob01d1fba5ab2d42e2585d0713db62c58ac4bed4b1
1 How to build and use Rhythmbox with DAAP music sharing support
3 * configure Rhythmbox with --enable-daap
4         Requires libsoup & either Howl or Avahi
5 * Start Howl's mDNSResponder or Avahi's avahi-daemon
6 * Start Rhythmbox, watch as remote music shares appear in your source list
7 * Click on a remote source, play their music.
8 * Check Edit->Preferences->Sharing to share your music library.
11 How Music Sharing Works
13 0. Definitions
14 1. mDNS/DNS-SD support 
15 2. Discovering new music shares
16 3. Connecting to a music share
17 4. Playing remote music
18 5. Publishing your music
19 6. RBDAAPStructure - structure parsing & building
21 0. Definitions / Fundamentals
23 DAAP stands for Digital Audio Access Protocol.  It is the communications
24 protocol that music sharing uses.  It is a structured (like xml) query/response
25 language that works over HTTP 1.1.  A DAAP server is nothing more than an 
26 HTTP server that responds to certain URI requests with a structured DAAP
27 response.  DAAP is the same music sharing protocol used by Apple iTunes.  So,
28 a copy of Rhythmbox that was compiled with DAAP will be able to browse iTunes
29 shares, and iTunes will be able to browse Rhythmbox's music.
31 mDNS is multicast DNS.  DNS Service Discovery (DNS-SD) sits on top of it.  
32 mDNS/DNS-SD is what Apple calls Bonjour or Rendezvous.  The fundamentals aren't especially important, suffice it to say it allows for automatic discovery of 
33 services on your local network.  You send out an mDNS/DNS-SD request for DAAP 
34 servers, it responds with all the servers that have published their information 
35 on your network.  mDNS/DNS-SD services have types.  The DAAP service type is 
36 _daap.  It is transported over tcp, so in the code you'll often see _daap._tcp, 
37 which is the full name for a DAAP service.  
39 iTunes hashing is an "authentication" method that appeared in later versions
40 of Apple iTunes.  It is a modified md5 hash of the URI being requested from a
41 DAAP server.  The hash is sent along in the HTTP headers to show an iTunes
42 server that the connecting client is "authenticated" (i.e. "is an iTunes
43 client").  The hashing code was borrowed with many thanks from libopendaap.
45 1. mDNS/DNS-SD support
47 For DAAP music sharing you'll need an mDNS/DNS-SD implementation.  
49 Howl (http://www.porchdogsoft.com/products/howl/) 
51 Avahi (http://www.freedesktop.org/Software/Avahi) 
53 are supported.  Either way, the mDNS/DNS-SD routines live in 
54 daapsharing/rb-daap-mdns.c/h.
56 There are three routines in the mDNS/DNS-SD libraries that we need.  Browsing to
57 discover remote shares, Resolving to figure out those shares' IP addresses, and
58 Publishing to make our own share known on the network.  There are three opaque
59 datatypes, RBDAAPmDNSBrowser, RBDAAPmDNSResolver, RBDAAPmDNSPublisher.  If
60 Rhythmbox has been compiled with Howl support, these will will be used as
61 sw_discovery_oid structures, which are used to identify the specific mDNS/DNS-SD
62 call.  If Rhythmbox is using Avahi, these datatypes represent 
63 AvahiServiceBrowser, AvahiServiceResolver and AvahiEntryGroup variables, 
64 which, again, are used to uniquely idenfity the specific mDNS/DNS-SD call.
66 Browsing for remote shares is started via rb_daap_mdns_browse() which takes a
67 callback that is run anytime a share is discovered or goes away.  Browsing will
68 continue until rb_daap_mdns_browse_cancel() is called.
70 Resolving is done asychronously via rb_daap_mdns_resolve() and a callback which
71 runs when the host is resolved or a timeout reached.  Either way, when the
72 the callback is run, the resolving stops.  If you wish to stop resolving early,
73 you can use rb_daap_mdns_resolve_cancel().
75 Pubsishing is done via rb_daap_mdns_publish().  The callback for this function
76 is used to notify you of success or failure due to name collision.  Two servers
77 cannot have the same name.  Publishing will continue until 
78 rb_daap_mdns_publish_cancel() is called.
80 3. Discovering new music shares
82 Discovery is started in shell/rb-shell.c:rb_shell_constructor().
83 rb_daap_sources_init() is called, which starts the discovery.  When new shares
84 are discovered, an RBDAAPSource is created and added to the source list.  When
85 shares disappear from the network, the source is removed from the list.  No
86 attempt is made to resolve or connect to the share until the user clicks on the
87 source.  This reduces unneccessary computations and network traffic.
89 4. Connecting to a music share
91 When a user clicks on an music share source in the list, 
92 rb-shell.c:rb_shell_select_source() runs the virtual function 
93 rb_source_activate().  This tells the RBDAAPSource to resolve the server via
94 RBDAAPmDNSResolver.  In the resolver's callback function, if the resolve was
95 successful, an RBDAAPConnection is created to the remote share.
97 RBDAAPConnection takes care of all the dirty work establishing a connection and
98 recieving a list of the share's music & playlists.  The HTTP communication is
99 done via libsoup.  There are 5 steps to establishing a new connection.  In each
100 several HTTP requests are made, and a DAAP structure returned.  The structure
101 is parsed (see RBDAAPStructure) and the appropriate information read out of
102 the parsed structure.
104 1. Get the server's version (/server-info)
105         Here we request /server-info to figure out what version of DAAP the
106 server is using.  Different versions requiring different iTunes hashing 
107 techniques.
109 2. Login (/login & /update)
110         Here we request /login to recieve a session-id.  All subsequent requests
111 must include the session-id as part of the URI (?session-id=X).  We also request
112 the initial revision-number.  Revision numbers are used to track changes in
113 the server's music library.  RHYTHMBOX DOES NOT YET SUPPORT TRACKING THESE
114 CHANGES.  The session-id is used by the server to identify your connection.
116 3. Get the database information (/databases)
117         We request /databases to get a list of databases on the server.  A list
118 is returned, but it is always only 1 element long, containing the ID number
119 of the music library on the server.
121 4. Get the song listing (/databases/ID/items)
122         We request /database/ID/items with a list of the metadata (artist,
123 album, title, etc) that we're interested in.  A lengthy list of all the songs in
124 the database and their metadata is returned.  Each song has a unique ITEM-ID 
125 number in the database.  The URI for requesting the actual song is 
126 /databases/ID/items/ITEM-ID.ITEM-FORMAT.  We create a RhythmDBEntry for each 
127 song, and add it to the RhythmDB.  A local hash table translating ITEM-ID to
128 uri is also populated (this will be used in step 5).
130 5. Get the playlists (/databases/ID/containers & 
131 /databases/ID/containers/CONTAINER-ID/items)
132         We request /databases/ID/containers to recieve a list of playlists. Each
133 playlist has a name and a unique CONTAINER-ID number.  For each palylist, a 
134 request of /databases/ID/containers/CONTAINER-ID/items is made, asking for the 
135 ITEM-IDs of all the songs in the playlist.  Each of these ITEM-IDs is resolved
136 into the uri using the hash table populated in step 4, and the uri add to the 
137 RBDAAPPlaylist.
139 After the connection is made and all the data transfered, a new 
140 RBDAAPPlaylistSource is created for each RBDAAPPlaylist in the connection.
141 These are added underneath the original RBDAAPSource.
143 4. Playing remote music
145 A URI for a DAAP music file looks like 
146 daap://HOST:PORT/databases/DATABASE-ID/items/ITEM-ID.ITEM-FORMAT?session-id=SESSION-ID
148 A new GStreamer source, RBDAAPSrc (existing in daapsharing/rb-daap-src.c/h) 
149 handles playing daap:// URIs.  It uses simple TCP sockets to open a connection
150 to the host, request the file (with the appropriate headers) and push the
151 recieved data through the pipeline.  The data recieved (the song) is in the
152 original format (be it mp3, ogg, aac, wav, whatever).  So GStreamer needs to
153 figure out how to decode the file before it pushes it the rest of the way
154 through the pipeline.  To do this, GStreamer uses its decodebin & typefind
155 elements. 
157 Every request for a song (even re-requests) must include several special 
158 headers, including the iTunes hash and a DAAP-Request-ID.  The Request-ID is 
159 simply a running tally of the number of songs we've requested.  It starts at 1,
160 and every time there is a new request it is incremented.  To determine the 
161 proper headers to send, RBDAAPsrc uses rb_daap_source_find_for_uri() and 
162 rb_daap_source_get_headers() which find the RBDAAPSource that contains a 
163 specific URI and fetch the headers needed for that URI.  
164 rb_daap_source_get_headers() hands the work off to 
165 rb_daap_connection_get_headers(), which keeps track of the Request-ID and
166 determines the iTunes hash.
168 Seeking in a DAAP stream is done by closing the existing connection and
169 establishing a new one, sending a Range: HTTP header with the appropriate
170 starting point.  However, we do not support standard GStreamer seeking.
171 This is because, if we were to, GStreamer's decodebin & typefind elements
172 abuse it, seeking several times in succession to determine the type of data
173 coming through the pipeline.  These requests will overwhelm an iTunes server,
174 prompting it to return an error after several of them.  To work around this,
175 RBPlayer handles seeking in DAAP streams via rb_daap_src_[set/get]_time().
176 This is a WORKAROUND until GStreamer is fixed or proper seeking can be figured
177 out.
179 5. Publishing your music
181 Publishing is started in shell/rb-shell.c:rb_shell_constructor() via the
182 rb_daap_sharing_init() call.  This creates an RBDAAPShare structure, which, in 
183 turn, creates a SoupServer structure to handle HTTP requests.  The server binds
184 on an available port, and this port along with the machine's IP address are
185 published to mDNS/DNS-SD via RBDAAPmDNSPublisher.
187 RBDAAPShare does several things.  It assigns unique ID numbers to every song
188 in the RhythmDB, and keeps an up to date mapping of RhythmDBEntry to ID and ID
189 to RhythmDBEntry via entry-added & entry-deleted signals from the RhythmDB.
190 THIS WILL EVENTUALLY BE USED TO SUPPORT SENDING MUSIC LIBRARY UPDATES.
191 For each request on the SoupServer, an appropriate DAAP response is built (see
192 RBDAAPStructure) and returned.  If the request is for a song file itself, the
193 file is opened via GnomeVFS and its contents returned.  Rhythmbox's DAAP server
194 implementation does not require iTunes hashing headers nor does it require
195 unique session-ids.
197 6. RBDAAPStructure - structure parsing & building
199 RBDAAPStructure is a data structure that handles parsing and building DAAP 
200 responses.  It lives in daapsharing/rb-daap-structure.c/h.
202 Useful reading:
204 http://www.deleet.de/projekte/daap/  The section titled 'DAAPD technicals'
205 http://molelog.molehill.org/blox/Computers/Macintosh/DAAP3.writeback (#1&2 also)
207 A DAAP response is of the form:
209 Byte:   1234            | 5678                  | length bytes
210 Data:   content code    | content length        | content
212 Codes are 4 byte character strings like 'msrv', 'mstt', 'mpro', and so on.
213 A list of codes is available in the RBDAAPContentCode enumeration in 
214 rb-daap-structure.h.  They are defined in the RBDAAPContentCodeDefintion array 
215 in rb-daap-structure.c.  There are several DAAP types.  You can ask the
216 DAAP server for /content-codes to retrieve a dictionary mapping codes to types,
217 or you can "just know."
219 Type       | C data type | Size
220 ------------------------------
221 Byte       | gchar       | 1
222 Signed Int | gchar       | 1
223 Short      | gint16      | 2
224 Int        | gint32      | 4
225 Date       | gint32      | 4
226 Int64      | gint64      | 8
227 Version    | gdouble     | 4
228 String     | gchar *     | Whatever the DAAP response says
229 Container  | GNode *     | Whatever the DAAP response says
231 Containers hold other data.  For example, the response from /server-info is:
233 dmap.serverinforesponse                 Container
234         dmap.status                     Int
235         dmap.protocolversion            Version
236         daap.protocolversion            Version
237         daap.itemname                   String
238         dmap.loginrequired              Int
239         ...
240 dmap.databasescoutn                     Int
243 The serverinforesponse is a Container holding the other content.
245 The bulk of the parsing work is done in 
246 rb_daap_structure_parse_container_buffer() The data & the length are passed in,
247 as well as a GNode * for the parent container.  The first pass is provided an
248 empty container to add all the "toplevel" content to.  The 4 byte content code 
249 is read and the daap type looked up accordingly.  The 4 byte data length 
250 (code size) is read.  The data is read into a GValue, with its type set 
251 accordingly.  The GValue & content code are added to the GNode * as new 
252 children.  If type is a container, rb_daap_structure_parse_container_buffer() is
253 called recursively.
255 There are find functions rb_daap_structure_find_item() & 
256 rb_daap_structure_find_node() which take a GNode * structure and content code
257 to be located.  The structure is walked until a node matching the content code
258 is found.  The node or the item is returned, depending on the function being
259 used.
261 To build a DAAP structure you use rb_daap_structure_add().  It creates a new 
262 node containing the data you add and appends it to the parent node you pass.  
263 You pass NULL for the parent to create a toplevel node.  The new node is 
264 returned.  The data you add is passed in as a content code and then the data.  
265 If the content type is Container, you don't need to pass any data.  Once the 
266 DAAP structure is build, use rb_daap_structure_serialize() to convert it to
267 a string appropriate for sending over a socket.