UMSP plugin development
Contents
So What Is This UMSP Thing Anyway?
UMSP is a service on the WDLXTV firmware that lets you view various types of media on your TV screen, this includes streaming audio and video content from any Internet source.
Each media item or stream requires a different flavor of plugin designed specifically to interface the media source with the WDTV.
You get a number of plugins out of the box, inclusive of SHOUTcast, BBC Audio broadcasts, and even a plugin that enables you to play DVD and CD disks connected to the WDTV.
Additional plugins can be obtained from SVN - the WD source repository, added by developers or even yourself if you care to work a little code.
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 WDTV can act as a UPnP MediaRenderer as you may have observed if you have a UPnP/DLNA compliant 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.
These are the items you see listed in the Media Servers section of your WDTV menus.
So how does this come into play with UMSP? Simply put the UMSP is a background process that acts as a UpnP MediaServer self publishing to your WDTV box.
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
UMSP has been developed as a web service with all coding in PHP. UMSP is fully integrated into the WDLXTV firmware; the details found on the originating article regarding configuration of the server on your WDTV no longer apply - it's all auto-magic.
Given the implementation both the Apache web-server and the UMSP daemon must be active on your WDTV to allow the process to publish content.
And, as you would expect the UMSP server shows up under the Media Servers section of your WDTV menus.
Plugin architecture
Content can be published via one or more complaint UMSP plug-ins, we'll discuss compliance shortly.
The UMSP server looks for compliant plugins on your WDTV, a plugin makes its presence known by initially registering with the UMSP server.
Registration is achieved by adding details to the $myMediaItems server side global.
There are a handful of plugins that do this automatically in the firmware but you can add others in a number of ways - under the hood they're all adding themselves to the $myMediaItems global.
You can add your own plugins to the server by creating an archive file umsp-plugins.tgz in the /tmp/conf folder on the WDTV
The archive contains the scripts that implement the plugin, in this example we're adding a script called revolution.php, your plugin may have additional files and/or folder structures - these would be contained within the archived too
tar -cvzf umsp-plugins.tgz revolution.php
mv -f umsp-plugins.tgz /tmp/conf
If you've prepared or obtained the archive from another location copy or move the archive to the /tmp/conf folder, this is a persistent folder in the WDTV firmware and your archive will be maintained across a reboot. When the WDTV boots the archive will be automatically unzipped to the /tmp/umsp-plugins folder where UMSP looks for plugin scripts
Along with the archive a script called umsp.php must be provided, this too is located in the /tmp/conf folder.
Note that there is limited space allocated for /tmp/conf so its best not to go too crazy with storing files to this location, the archive and umsp.php file are pretty small so it's doubtful they'll cause issues. There are potential changes in the works that will change where the UMSP plugins can be located and the use of /tmp/conf will no longer be a bottleneck.
The umsp.php script contains one or more entries that add to the server global in effect registering the presence of the plugin. The details provided are attributes that will be published by the UMSP server - they define the attributes of the UPnP entity that may be consumed by any UPnP compliant device capable of rendering said entity - in our case that would be the WDTV box.
In this example we register the revolution plugin we added to the archive, we'll develop this plugin throughout this article
<?php
global $myMediaItems; # server side global containing registered plugins # --r-e-v-o-l-u-t-i-o-n---b-a-b-y------> $myMediaItems[] = array ( 'id' => 'umsp://plugins/revolution', 'parentID' => '0', 'dc:title' => 'REVO-lution - an example UMSP plug-in', 'upnp:class' => 'object.container', # many other attributes are supported that comply to UPnP protocol, these are all optional # here we give our REVO-lution plug-in some badge art 'upnp:album_art' => 'http://lh6.ggpht.com/_xJcSFBlLg_Y/TRq3jGGDncI/AAAAAAAAAJ8/A1TLqM9trAI/s200/upnp-items.png', );
?>
At a minimum you'll need to provide the plugin with an id, a title and a upnp class.
The id tells the UMSP server where to find your plugin script, in this example it'll look for a script file located under /tmp/umsp-plugins called revolution.php, the web server sees this path via the umnsp://plugins/ naming scheme - if you dig you'll see that the plugins folder under the UMSP server implementation is a symlink to the actual umsp-plugins folder
The title is what will be displayed on the UMSP menu, in this case the REVO-lution - an example UMSP plug-in will show up on the initial UMSP menu along with the built in plugins; for those wanting a Hello World example substitute that string now.
The upnp:class tells the UPnP complaint device that this entity is a container, basically a folder, that contains additional entities, be they other containers or media items that the device can render. Again if you require lots more details see the UPnP documentation. In UMSP plugin development we normally only deal with the following flavors of upnp:class
upnp:class | description |
---|---|
object.container | folder of media items, sub-folders etc |
object.item.videoItem | video steam or file |
object.item.audioItem | audio steam or file |
object.item.imageItem | image, photo or other |
Plugins that are sourced from SVN use the the same $myMediaItems to register themselves when they are activated. Instead of being unzipped from an archive the plugin code is pulled from SVN on reboot, this is why any code changes you make to these plugins will be lost each time you reboot. Given we're working in a Linux environment there are several mechanisms to make your changes persist - we'll discuss these towards the end of the article.
You can browse the SVN repository here http://svn.wdlxtv.com/listing.php?repname=UMSP feel free to see whats available and browse through the code for examples if your looking to start development.
It's hoped that over time the UMSP plugin repository will grow, it needs plugin contributions from developers - thats you right!
Once registered the plugin must pass a second level of checks to assess it is compliant with the UMSP service.
In the main plugin script you must provide at minimum an entry point interface that the UMSP server will use to load the plugins content
Plugin Interfaces _pluginMain
When a user selects the plugins menu item the UMSP server loads the plugin script and makes a call to the main plugin entry API - named as we'd expect _pluginMain. The plugin must contain the _pluginMain function.
The intent of the function is to return addition UPnP complaint entities that the WDTV can render and display on your TV; that's about all you need to be complaint, registered in the server side global and have a _pluginMain interface
The _pluginMain should always return an array of items, if no items are returned you'll see the "No Media In Folder" message
Here we see a code snippet from the revolution.php script showing _pluginMain
<?php
# _pluginMain must be provided, it is the entry point to publish content for the plugin # this will automatically be called when a user select the main menu item (or others) # from the UMSP menu function _pluginMain($prmQuery) { parse_str($prmQuery, $queryData); # parse query string to name value pair arguments - this is assumes the '&' delimiter if (isset($queryData['menu'])&&($queryData['menu']!=)) $items = _pluginCreateItems($queryData['menu]); # call a plugin specific function passing the attribute, see below for details else $items = _pluginCreateMyItemsList(); # call a plugin specific function to generate a list of renderable items return $items; # always return items for the plugin } # end function
The UMSP server automatically passes any URL query arguments it receives for the respective plugin in through the call so they can be further evaluated. From the initial UMSP menu the query string will be empty. If the plugin needs to evaluate these arguments you should code accordingly.
In the above example when the query string is empty, called from the main menu, the plugin calls a second function that returns a list of UPnP items, these could be additional containers or media items that the WDTV can play and display on your TV screen
Beyond the required naming convention used for the UMSP interface functions you can name your underlying functions whatever you wish, the original plugin authors used the same naming strategy as the required interfaces but its your call, no pun intended, as to whether you follow the same route.
In the above example the main function makes a call to another function called _pluginCreateMyItemsList, from a main menu call, lets take a look at what it provides.
Note that the following are for example purposes, the content may or may not render in your browser.
The example returns UPnP items for video, image, audio, and menu items that can be rendered and played via the WDTV
# return a couple of media items, a hard coded list of media items for now function _pluginCreateMyItemsList() { $items = array(); # initialize your return array - not mandatory but good practice if your plugin has some complex branching logic items[] = array ( 'id' => 'umsp://plugins/revolution?exampleid=1', # just an example 'dc:title' => 'UMSP Isn\'t bad - It won\'t make your head spin', # upnp:class - when is not a container there is a resource associated - this is the video, image or audio track # here we have a movie for your enjoyment stored locally 'res' => 'file://path/to/video/spinning_earth.avi', 'upnp:class' => 'object.item.videoItem', 'protocolInfo' => 'http-get:*:*:*', # wild-carded - let the UPnP renderer work out the details ); items[] = array ( 'id' => 'umsp://plugins/revolution?exampleid=2', 'dc:title' => 'Media Item is an image - click me', 'res' => 'http://veimages.gsfc.nasa.gov/2429/globe_east_2048.jpg', 'upnp:class' => 'object.item.imageItem', 'protocolInfo' => 'http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG', # some protocol detail provided this time ); items[] = array ( 'id' => 'umsp://plugins/revolution?exampleid=3', # just an example 'dc:title' => 'REVO-lution Baby!!!', 'res' => 'http://v22.nonxt2.googlevideo.com/videoplayback?id=c339b9bb5198af3a&itag=18&begin=0&len=2147483647&app=picasa&et=INVALID&el=INVALID&ip=0.0.0.0&ipbits=0&expire=2871264432&sparams=id,itag,ip,ipbits,expire&signature=317AA7B94080F0A653A84DDF6E326D480EBB42CD.6DFA60B55F3D57D47A8C7AEFCEAA9E38E8D10BDB&key=ck1', 'upnp:class' => 'object.item.videoItem', 'protocolInfo' => 'http-get:*:video/mpeg4:*', # we know the format, see UPnP documentation for additional protocolInfo attributes ); items[] = array ( 'id' => 'umsp://plugins/revolution?exampleid=4', 'dc:title' => 'An audio stream - BBC content', 'res' => 'http://downloads.bbc.co.uk/podcasts/radio1/reviewshow/reviewshow_20110105-1214a.mp3', 'upnp:class' => 'object.item.audioItem', 'protocolInfo' => 'http-get:*:audio/mpeg:*', # some protocol detail for an mp3 audio stream # items can have thumbnails too! 'upnp:album_art'=> 'http://lh4.ggpht.com/_xJcSFBlLg_Y/TRSIeDbvcbI/AAAAAAAAAIY/5hBsQUQode0/s200/bbc200x200slate.png', ); # if you're passing arguments format them accordingly, some twists and tricks that we'll discuss shortly $dataStr = http_build_query(array('menu' => 'Looking for an Argument')); items[] = array ( 'id' => 'umsp://plugins/revolution?'.$dataStr, # the resource here is a call to ourself 'res' => 'umsp://plugins/revolution?'.$dataStr, 'dc:title' => 'Sub-menu - more items here', 'upnp:class' => 'object.container', 'upnp:album_art'=> 'http://lh6.ggpht.com/_xJcSFBlLg_Y/TRq3jGGDncI/AAAAAAAAAJ8/A1TLqM9trAI/s200/upnp-items.png', ); return $items; }
The important attributes here are the resource identifier, this is a UNC path to a local or remote resource. It can be as simple as a file or as relatively complex as a URL for a proxied stream with one or more query attributes - a fat URL parameterized to drive downstream functionality.
The protocolInfo informs UPnP how the resource is to be rendered, in most plugins you'll provide as much information as you can up front, you may be lucky and get away with wild-carding the information and let the downstream rendering device work out the details; rule of thumb - if you know what you're dealing with and can provide the information do so up front.
Note for a container that will display a sub-menu we all ourselves passing the arguments that can be evaluated in the _pluginMain call. If you know of a plug that can fulfill some of the functionality you require there's nothing stopping you calling the other plugin as resource too. We'll cover the building blocks provided in the firmware that make plugin development a little easier shortly.
Plugin Interfaces _pluginSearch
The UMSP server also provides an interface call for search functionality, much like _pluginMain it is logically named - _pluginSearch. A user can enter a search with the plugin menu up on screen, the plug needs to be active so that the server can determine who to call when you press search. Under the hood the UMSP server is keeping track of what plugin is currently active.
Much like the _pluginMain call _pluginSearch accept query string attributes, these ill contain the detailed search attributes.
Depending from what main menu the UMSP server was activated, Video, Photo, or Music, the WDTV will display various search options
Main Menu | Available Search Options |
---|---|
Video | Allows search by Title |
Photo | Allows search by Title |
Music | Allows search by Title, Album and Genre |
If you don't need that level of detail you can write a generic search that parses for any of these variations using a simple regular expression evaluation; similarly modify the regex to match the level of detail you wish to attain
Once again _pluginSearch returns UPnP items
function _pluginSearch($prmQuery) { # here we have a generic regex - just get the search term - we don't care what WDTV search menu was used if (preg_match('/and dc:(title|album|genre) contains "(.*?)"/', $prmQuery, $searchterm)) { # do something with the search term - here we pass to Picasa for evaluation $param = array ( 'albumurl' => urlencode("http://picasaweb.google.com/data/feed/api/all?q={$searchterm[1]}"), 'propername' => 'User Search', ); return _pluginCreateMoreItems($param); } else return null; }
Additional details on implementing search can be found in the main forum http://forum.wdlxtv.com/viewtopic.php?f=49&t=1198
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