UMSP plugin development
This page is under development and may not be very useful --Mad ady 12:51, 16 December 2010 (UTC)
Contents
How UMSP works
Universal Plug and Play is a set of computer network protocols from the UPnP Forum. The goals of UPnP are to allow UPnP devices on a network to detect each other and connect seamlessly, and to simplify the implementation of networks in the home (data sharing, communications, and entertainment) and corporate environments. The UPnP architecture supports zero-configuration, which implies no need for manual configuration on the user's end. There are typically three types of UPnP AV (Audio/Video) device control protocols (also called profiles); "UPnP AV MediaServer" whose sole purpose is to share content, "UPnP AV MediaRenderer" which renders content and exposes an interface to control the playback, and a "UPnP AV MediaServer ControlPoint" which can detect/find "UPnP AV MediaServers" and browse them to read media from them. A DMP (Digital Media Player) typically only implements a UPnP AV MediaServer ControlPoint, to be able to play files from UPnP AV MediaServers. The WD can act as a UpnP client as you may have observed if you have a UpnP/DLNA device on your network; for example a Windows 7 machine, or you're running a specific UpnP software package like TVersity, Twonky and many others.
So how does this come into play with UMSP? Simply put the UMSP process is a UpnP MediaServer that self publishes to your WD box. Plug-ins may be developed that publish content that can then be played (rendered) on the WD device.
For a more detailed explanation on how this is achieved refer to the UMSP originating thread presented by Zoster http://wdtvforum.com/main/index.php?topic=4459.0
Plugin architecture
- TODO - explain how the plugins are structured and how they intercommunicate
Plugin API
Here is a list of functions that can be used by your plugins, provided by the UMSP server:
- TODO
Sample plugin
- TODO - when I learn a bit more about the process :)
Plugins with proxies
A proxy is by definition an entity that behaves like the real target server, but it's not. Proxies are useful for when you need to do some extra steps in order to get the data you want, but you want to do this transparently for other processes.
Basically a proxy gets called like any other media file. The MediaPlayer process will query the proxy like it's the end server and will request details about the media that is to be played (e.g. Content-length), and it will also request the media file (or bits and pieces of it).
In the MSC ([[1]]) above a few interactions are shown between the player, the proxy and the server.
The player first makes a request to the "server" (which is the proxy plugin) in order to find more about the media it's supposed to play. The parameters of every query between the MediaPlayer and the Proxy are sent as HTTP header entries and can be accessed in the PHP variable $_SERVER. The first query usually asks for the server's header in order to get the media's Content-length and to see if streaming is supported by the server.
Here's a sample view of the $_SERVER variable in a serialized way for the first query:
a:28:{ s:9:"UNIQUE_ID";s:24:"TSBV538AAAEAAA6qC-sAAAAB"; s:10:"SCRIPT_URL";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:10:"SCRIPT_URI";s:83:"http://127.0.0.1/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:9:"HTTP_HOST";s:12:"127.0.0.1:80"; s:32:"HTTP_GETCONTENTFEATURES_DLNA_ORG";s:1:"1"; s:26:"HTTP_TRANSFERMODE_DLNA_ORG";s:9:"Streaming"; s:15:"HTTP_USER_AGENT";s:49:"INTEL_NMPR/2.1 DLNADOC/1.50 dma/3.0 alphanetworks"; s:4:"PATH";s:103:"/apps/vim/bin:/apps/mc/bin:/apps/iftop/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; s:16:"SERVER_SIGNATURE";s:115:"<address>Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13 Server at 127.0.0.1 Port 80</address>"; s:15:"SERVER_SOFTWARE";s:67:"Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13"; s:11:"SERVER_NAME";s:9:"127.0.0.1"; s:11:"SERVER_ADDR";s:9:"127.0.0.1"; s:11:"SERVER_PORT";s:2:"80"; s:11:"REMOTE_ADDR";s:9:"127.0.0.1"; s:13:"DOCUMENT_ROOT";s:8:"/var/www"; s:12:"SERVER_ADMIN";s:19:"webmaster@localhost"; s:15:"SCRIPT_FILENAME";s:75:"/var/www/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:11:"REMOTE_PORT";s:5:"40702"; s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1"; s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.0"; s:14:"REQUEST_METHOD";s:4:"HEAD"; s:12:"QUERY_STRING";s:20:"video_id=FPfiY33nqgo"; s:11:"REQUEST_URI";s:88:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php?video_id=FPfiY33nqgo"; s:11:"SCRIPT_NAME";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:8:"PHP_SELF";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:12:"REQUEST_TIME";i:1293964775; s:4:"argv";a:1:{i:0;s:20:"video_id=FPfiY33nqgo";}s:4:"argc";i:1;}
Most notable in this query is the use of REQUEST_METHOD=HEAD (to avoid asking for the whole file). The proxy script should ask for the video file (also with a HEAD query) and should return back the server's HTTP headers. The proxy should add an extra header as Content-Disposition: attachment; filename="video.flv" if none is provided (change the filename name and extension appropriately).
Based on the first query, the MediaPlayer could make several more requests in order to get more information. One such query might be "play from the start":
a:28:{ s:9:"UNIQUE_ID";s:24:"TSBV6H8AAAEAAAjPBNEAAAAA"; s:10:"SCRIPT_URL";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:10:"SCRIPT_URI";s:83:"http://127.0.0.1/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:9:"HTTP_HOST";s:12:"127.0.0.1:80"; s:14:"CONTENT_LENGTH";s:1:"0"; s:26:"HTTP_TRANSFERMODE_DLNA_ORG";s:9:"Streaming"; s:15:"HTTP_USER_AGENT";s:49:"INTEL_NMPR/2.1 DLNADOC/1.50 dma/3.0 alphanetworks"; s:4:"PATH";s:103:"/apps/vim/bin:/apps/mc/bin:/apps/iftop/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; s:16:"SERVER_SIGNATURE";s:115:"<address>Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13 Server at 127.0.0.1 Port 80</address>"; s:15:"SERVER_SOFTWARE";s:67:"Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13"; s:11:"SERVER_NAME";s:9:"127.0.0.1"; s:11:"SERVER_ADDR";s:9:"127.0.0.1"; s:11:"SERVER_PORT";s:2:"80"; s:11:"REMOTE_ADDR";s:9:"127.0.0.1"; s:13:"DOCUMENT_ROOT";s:8:"/var/www"; s:12:"SERVER_ADMIN";s:19:"webmaster@localhost"; s:15:"SCRIPT_FILENAME";s:75:"/var/www/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:11:"REMOTE_PORT";s:5:"40705"; s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1"; s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.0"; s:14:"REQUEST_METHOD";s:3:"GET"; s:12:"QUERY_STRING";s:20:"video_id=FPfiY33nqgo"; s:11:"REQUEST_URI";s:88:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php?video_id=FPfiY33nqgo"; s:11:"SCRIPT_NAME";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:8:"PHP_SELF";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:12:"REQUEST_TIME";i:1293964776; s:4:"argv";a:1:{i:0;s:20:"video_id=FPfiY33nqgo";}s:4:"argc";i:1;}
Here you can see that the Content-Length is set to zero (we're not sending anything), the Request-method is GET. The proxy should reply to the MediaPlayer with the header received from the server, adding Content-Disposition (as above) and passing the output from the proxy to the server (e.g. using fpassthru($fp))
If the user decides to navigate inside the video, the MediaPlayer process will receive DLNA commands (like MediaPlay(64 1)) telling it to jump to a specific point in time (e.g. 64 seconds into the video). Fortunately, the MediaPlayer process will convert the time it receives to a specific byte offset in the media stream and will make a request to the proxy to fetch data starting from that offset (using HTTP Ranges requests - see http://labs.apache.org/webarch/http/draft-fielding-http/p5-range.html). If the server supports ranges, it will return data from that offset, and it's the MediaPlayer's job to render it. The proxy just has to forward the request to the server.
Here is a request for such a jump, starting from offset 238192480 (238MB into the video):
a:28:{ s:9:"UNIQUE_ID";s:24:"TSBYSX8AAAEAAAjPBNIAAAAA"; s:10:"SCRIPT_URL";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:10:"SCRIPT_URI";s:83:"http://127.0.0.1/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:9:"HTTP_HOST";s:12:"127.0.0.1:80"; s:10:"HTTP_RANGE";s:16:"bytes=238192480-"; s:26:"HTTP_TRANSFERMODE_DLNA_ORG";s:9:"Streaming"; s:15:"HTTP_USER_AGENT";s:49:"INTEL_NMPR/2.1 DLNADOC/1.50 dma/3.0 alphanetworks"; s:4:"PATH";s:103:"/apps/vim/bin:/apps/mc/bin:/apps/iftop/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; s:16:"SERVER_SIGNATURE";s:115:"<address>Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13 Server at 127.0.0.1 Port 80</address>"; s:15:"SERVER_SOFTWARE";s:67:"Apache/2.2.15 (Debian) PHP/5.3.2-1 with Suhosin-Patch mod_scgi/1.13"; s:11:"SERVER_NAME";s:9:"127.0.0.1"; s:11:"SERVER_ADDR";s:9:"127.0.0.1"; s:11:"SERVER_PORT";s:2:"80"; s:11:"REMOTE_ADDR";s:9:"127.0.0.1"; s:13:"DOCUMENT_ROOT";s:8:"/var/www"; s:12:"SERVER_ADMIN";s:19:"webmaster@localhost"; s:15:"SCRIPT_FILENAME";s:75:"/var/www/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:11:"REMOTE_PORT";s:5:"45030"; s:17:"GATEWAY_INTERFACE";s:7:"CGI/1.1"; s:15:"SERVER_PROTOCOL";s:8:"HTTP/1.0"; s:14:"REQUEST_METHOD";s:3:"GET"; s:12:"QUERY_STRING";s:20:"video_id=FPfiY33nqgo"; s:11:"REQUEST_URI";s:88:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php?video_id=FPfiY33nqgo"; s:11:"SCRIPT_NAME";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:8:"PHP_SELF";s:67:"/umsp/plugins/youtube-subscriptions/youtube-subscriptions-proxy.php"; s:12:"REQUEST_TIME";i:1293965385; s:4:"argv";a:1:{i:0;s:20:"video_id=FPfiY33nqgo";}s:4:"argc";i:1;}
The difference between this request and the request above (the one for playback) is that now "Content-length=0" is no longer set and there's a new parameter called "HTTP_RANGE" that tells the server exactly where to go. The proxy should forward these parameters to the server and should return the content to the player just like above.
Note: You can enable debugging in the youtube-subscriptions-proxy.php (switch on $want_debug) and you will get more detailed debug output in /var/log/php5/error_log. The output will also show the headers that are sent and received from communicating with the server, and it will show what gets sent back to the MediaPlayer client.
Note2: Some relevant links about this subject: [2], [3], [4]
Logs
You can log your errors/warnings/etc to /var/www/php5/error_log by using
error_log("whatever you're trying to log");
As a best practice rule you can add a script name and a sevrity to help with filtering later on:
error_log("yt-subscriptions-proxy: INFO: whatever you're trying to log");
It is recommended (for your own troubleshooting ease) to enable logs only when you need them and disable them (except for errors) before commiting to SVN.
The same log file will hold syntax errors and other PHP errors. More information: http://forum.wdlxtv.com/viewtopic.php?f=3&t=2756&start=0
Troubleshooting
- You can test out and troubleshoot UMSP plugins (including proxies) by using the built-in umsp-test.php script. Point your browser to http://wdtvlive-IP/umsp/umsp-test.php
- User PaulF submitted a test script for UMSP plugins that can show output and errors. Save the file as /tmp/www-tmp/umsp2html.php:
http://forum.wdlxtv.com/viewtopic.php?f=3&t=2756#p21358. On your PC browse to: http://wdtvlive-IP/tmp/umsp2html.php?path=0
- You can start the MediaPlayer process (which handles playback) in debug mode to observe what's going on. Details here: http://forum.wdlxtv.com/viewtopic.php?f=53&t=517#p3287. You will be getting a lot of debug output including the DLNA commands for skipping, pausing, etc:
killall MediaLogic MediaLogic AV MSGL_DBG