[vlc-devel] [PATCH 2/2] LUA: AirPlay target support

Brendon Justin brendonjustin at gmail.com
Mon Feb 27 22:59:18 CET 2012


Handles the AirPlay protocol, allowing devices to connect and send video
to VLC.  No photo slideshow support yet.
---
 modules/lua/intf.c         |   13 ++
 modules/lua/vlc.c          |    9 +
 modules/lua/vlc.h          |    3 +
 share/Makefile.am          |    2 +
 share/lua/intf/airplay.lua |  366 ++++++++++++++++++++++++++++++++++++++++++++
 src/interface/interface.c  |    5 +
 6 files changed, 398 insertions(+), 0 deletions(-)
 create mode 100644 share/lua/intf/airplay.lua

diff --git a/modules/lua/intf.c b/modules/lua/intf.c
index 2a8fea8..452905c 100644
--- a/modules/lua/intf.c
+++ b/modules/lua/intf.c
@@ -123,6 +123,12 @@ static char *MakeConfig( intf_thread_t *p_intf, const char *name )
             free( psz_rc_host );
         }
     }
+#ifdef __APPLE__
+    else if( !strcmp( name, "airplay" ) )
+    {
+        asprintf( &psz_config, "airplay" );
+    }
+#endif
 
     return psz_config;
 }
@@ -428,3 +434,10 @@ int Open_LuaTelnet( vlc_object_t *p_this )
 {
     return Start_LuaIntf( p_this, "telnet" );
 }
+
+#ifdef __APPLE__
+int Open_LuaAirplay( vlc_object_t *p_this )
+{
+    return Start_LuaIntf( p_this, "airplay" );
+}
+#endif
diff --git a/modules/lua/vlc.c b/modules/lua/vlc.c
index 3e3047b..2af0185 100644
--- a/modules/lua/vlc.c
+++ b/modules/lua/vlc.c
@@ -130,6 +130,15 @@ vlc_module_begin ()
         /* add_shortcut( "luahotkeys" ) */
         /* add_shortcut( "hotkeys" ) */
 
+#ifdef __APPLE__
+    add_submodule ()
+        set_section( N_("Lua AirPlay"), 0 )
+        set_capability( "interface", 0 )
+        set_callbacks( Open_LuaAirplay, Close_LuaIntf )
+        set_description( N_("Lua AirPlay") )
+        add_shortcut( "luaairplay", "airplay" )
+#endif
+
     add_submodule ()
         set_shortname( N_( "Lua Meta Fetcher" ) )
         set_description( N_("Fetch meta data using lua scripts") )
diff --git a/modules/lua/vlc.h b/modules/lua/vlc.h
index 2b8fbff..ba4470a 100644
--- a/modules/lua/vlc.h
+++ b/modules/lua/vlc.h
@@ -62,6 +62,9 @@ void Close_LuaIntf( vlc_object_t * );
 int Open_LuaHTTP( vlc_object_t * );
 int Open_LuaCLI( vlc_object_t * );
 int Open_LuaTelnet( vlc_object_t * );
+#ifdef __APPLE__
+int Open_LuaAirplay( vlc_object_t * );
+#endif
 
 
 int Open_Extension( vlc_object_t * );
diff --git a/share/Makefile.am b/share/Makefile.am
index 75e22e5..2dddd41 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -209,6 +209,7 @@ if BUILD_HTTPD
 nobase_vlclib_DATA += \
 	lua/intf/http.luac \
 	lua/intf/modules/httprequests.luac \
+	lua/intf/airplay.luac \
 	$(NULL)
 nobase_vlcdata_DATA += $(DIST_http_lua)
 endif
@@ -219,6 +220,7 @@ EXTRA_DIST += \
 	lua/README.txt \
 	lua/extensions/README.txt \
 	lua/intf/README.txt \
+	lua/intf/airplay.lua \
 	lua/intf/cli.lua \
 	lua/intf/dummy.lua \
 	lua/intf/dumpmeta.lua \
diff --git a/share/lua/intf/airplay.lua b/share/lua/intf/airplay.lua
new file mode 100644
index 0000000..3a248fb
--- /dev/null
+++ b/share/lua/intf/airplay.lua
@@ -0,0 +1,366 @@
+--[==========================================================================[
+ airplay.lua: HTTP AirPlay target support module for VLC
+--[==========================================================================[
+ Copyright (C) 2012 the VideoLAN team and authors
+ $Id$
+
+ Authors: Brendon Justin <brendonjustin at gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+
+ The AirPlay protocol and the strings APPLETV_SERVER_INFO and PLAYBACK_INFO
+ are based on and copied from, respectively, code in Airplayer:
+ https://github.com/PascalW/Airplayer
+--]==========================================================================]
+
+--[==========================================================================[
+Configuration options:
+ * none (port must be passed as a command-line argument to VLC)
+--]==========================================================================]
+
+-- Lua modules
+require "common"
+require "string"
+
+-- TODO: use these
+local mimes = {
+    biplist = "application/x-apple-binary-plist",
+    plist = "text/x-apple-plist+xml",
+}
+
+local APPLETV_SERVER_INFO = [[<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>deviceid</key>
+<string>58:55:CA:06:BD:9E</string>
+<key>features</key>
+<integer>119</integer>
+<key>model</key>
+<string>AppleTV2,1</string>
+<key>protovers</key>
+<string>1.0</string>
+<key>srcvers</key>
+<string>101.10</string>
+</dict>
+</plist>]]
+
+PLAYBACK_INFO = [[<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>duration</key>
+<real>%f</real>
+<key>loadedTimeRanges</key>
+<array>
+    <dict>
+        <key>duration</key>
+        <real>%f</real>
+        <key>start</key>
+        <real>0.0</real>
+    </dict>
+</array>
+<key>playbackBufferEmpty</key>
+<true/>
+<key>playbackBufferFull</key>
+<false/>
+<key>playbackLikelyToKeepUp</key>
+<true/>
+<key>position</key>
+<real>%f</real>
+<key>rate</key>
+<real>%d</real>
+<key>readyToPlay</key>
+<true/>
+<key>seekableTimeRanges</key>
+<array>
+    <dict>
+        <key>duration</key>
+        <real>%f</real>
+        <key>start</key>
+        <real>0.0</real>
+    </dict>
+</array>
+</dict>
+</plist>]]
+
+-- POST
+function callback_reverse(data, url, request, type, addr, host)
+    vlc.msg.dbg("callback_reverse")
+
+    --  Requests don't seem to finish without the content-length header
+    --  and some placeholder text
+    return [[Status: 101
+Upgrade: PTTH/1.0
+Connection: Upgrade
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- POST
+function callback_play(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_play")
+    if in_var then
+        vlc.msg.dbg("in_var: "..in_var)
+        local i, j = string.find(in_var, "Location: ")
+        local k = string.find(in_var, "Start")
+        local playback_url = string.sub(in_var, j+1, k-2)
+        i, j = string.find(in_var, "Position: ")
+        local start_time = string.sub(in_var, j+1)
+
+        vlc.playlist.add({{path=vlc.strings.make_uri(playback_url)}})
+
+        local input = vlc.object.input()
+        if input then
+            vlc.var.set(input, "position", start_time)
+        end
+    end
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- POST
+-- GET
+-- type 2 is get, type 4 is post
+function callback_scrub(data, url, request, type, in_var, addr, host)
+    if type and type == 2 then
+        vlc.msg.dbg("callback_scrub (GET)")
+    elseif type == 4 then
+        vlc.msg.dbg("callback_scrub (POST)")
+    end
+    
+    local scrub_body = nil
+
+    if type == 2 then
+        -- GET
+        scrub_body = [[duration: %f
+position: %f
+]]
+        local input = vlc.object.input()
+
+        if input then
+            local duration = nil
+            local position = nil
+
+            duration = vlc.var.get(input,"length")
+            if not duration then
+                vlc.msg.dbg("duration nil")
+                duration = 0
+            else
+                vlc.msg.dbg("duration: "..duration)
+            end
+
+            position = vlc.var.get(input,"time")
+            if not position then
+                vlc.msg.dbg("position nil")
+                position = 0
+            else
+                vlc.msg.dbg("position: "..position)
+            end
+
+            scrub_body = string.format(scrub_body, duration, position)
+            
+            return [[Content-Length: ]]..string.len(scrub_body)..[[
+
+
+]]..scrub_body..[[
+
+]]
+        end
+    elseif type == 4 then
+        -- POST
+        local position = nil
+        request = string.gsub(request, "position=", "")
+        position = tonumber(request)
+        common.seek(math.floor(position))
+    end
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- POST
+function callback_rate(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_rate")
+
+    if request then
+        rate_string = string.gsub(request, "value=", "")
+        rate = tonumber(rate_string)
+        
+        if rate == 0 then
+            vlc.playlist.pause()
+        else
+            vlc.playlist.play()
+        end
+    end
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- PUT
+-- Note that this doesn't work yet
+function callback_photo(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_photo, ignored (unsupported)")
+
+    if type == 5 then
+        if in_var then
+            vlc.msg.dbg("in_var: "..in_var)
+        end
+    end
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- POST
+-- GET
+-- type 2 is get, type 4 is post
+function callback_authorize(data, url, request, type, in_var, addr, host)
+    vlc.msg.warn("Trying to play DRM protected video, currently unsupported.")
+
+    if type == 2 then
+        vlc.msg.dbg("callback_authorize (GET), ignored (unsupported)")
+    elseif type == 4 then
+        vlc.msg.dbg("callback_authorize (POST), ignored (unsupported)")
+    end
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- GET
+function callback_server_info(data, url, request, type, addr, host)
+    vlc.msg.dbg("callback_server_info")
+
+    return [[Content-Type: text/x-apple-plist+xml
+Content-Length: ]]..string.len(APPLETV_SERVER_INFO)..[[
+
+
+]]..APPLETV_SERVER_INFO..[[
+
+]]
+end
+
+-- GET
+function callback_slideshow_features(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_slideshow_features")
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+-- GET
+-- Note that this doesn't set the scrub position on the device properly
+function callback_playback_info(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_playback_info")
+
+    local info_string = ""
+    local duration =  0
+    local position = 0
+    local playing = 0
+
+    local input = vlc.object.input()
+
+    if input then
+        duration = vlc.var.get(input,"length")
+        if not duration then
+            duration = 0
+        else
+            vlc.msg.dbg("duration: "..duration)
+        end
+
+        position = vlc.var.get(input,"time")
+        if not position then
+            position = 0
+        else
+            vlc.msg.dbg("position: "..position)
+        end
+    end
+
+    if vlc.playlist.status() == "playing" then
+        playing = 1
+    else
+        playing = 0
+    end
+
+    info_string = string.format(PLAYBACK_INFO, duration, duration, position, playing, duration)
+
+    return [[Content-Type: text/x-apple-plist+xml
+Content-Length: ]]..string.len(info_string)..[[
+
+
+]]..info_string..[[
+
+]]
+end
+
+-- POST
+function callback_stop(data, url, request, type, in_var, addr, host)
+    vlc.msg.dbg("callback_stop")
+    vlc.playlist.stop()
+
+    return [[Status: 200
+Content-Length: 18
+
+Placeholder text.
+]]
+end
+
+if config then
+    if config.host then
+        vlc.msg.err("\""..config.host.."\" HTTP host ignored")
+        local port = string.match(config.host, ":(%d+)[^]]*$")
+        vlc.msg.info("Pass --http-host=IP "..(port and "and --http-port="..port.." " or "").."on the command line instead.")
+    end
+end
+
+--  Start broadcasting AirPlay support via Bonjour/Avahi
+bc = vlc.airplay_bonjour()
+h = vlc.httpd()
+
+local reverse = h:handler("/reverse",nil,nil,nil,callback_reverse,nil)
+local play = h:handler("/play",nil,nil,nil,callback_play,nil)
+local scrub = h:handler("/scrub",nil,nil,nil,callback_scrub,nil)
+local rate = h:handler("/rate",nil,nil,nil,callback_rate,nil)
+local photo = h:handler("/photo",nil,nil,nil,callback_photo,nil)
+local authorize = h:handler("/authorize",nil,nil,nil,callback_authorize,nil)
+local srv_info = h:handler("/server-info",nil,nil,nil,callback_server_info,nil)
+local slideshow_fts = h:handler("/slideshow-features",nil,nil,nil,callback_slideshow_features,nil)
+local playback_info = h:handler("/playback-info",nil,nil,nil,callback_playback_info,nil)
+local stop = h:handler("/stop",nil,nil,nil,callback_stop,nil)
+
+while not vlc.misc.lock_and_wait() do end -- everything happens in callbacks
diff --git a/src/interface/interface.c b/src/interface/interface.c
index 11068ce..b5d3067 100644
--- a/src/interface/interface.c
+++ b/src/interface/interface.c
@@ -96,6 +96,11 @@ int intf_Create( vlc_object_t *p_this, const char *chain )
     var_Change( p_intf, "intf-add", VLC_VAR_ADDCHOICE, &val, &text );
     val.psz_string = (char *)"http";
     text.psz_string = (char *)_("Web");
+#if defined( __APPLE__ )
+    var_Change( p_intf, "intf-add", VLC_VAR_ADDCHOICE, &val, &text );
+    val.psz_string = (char *)"airplay";
+    text.psz_string = (char *)_("AirPlay");
+#endif
     var_Change( p_intf, "intf-add", VLC_VAR_ADDCHOICE, &val, &text );
     val.psz_string = (char *)"logger";
     text.psz_string = (char *)_("Debug logging");
-- 
1.7.7.5 (Apple Git-26)




More information about the vlc-devel mailing list