11

I'd like to write a very simple plugin for VLC that makes web requests when a media is played, paused, or stopped. It is a very similar to a scrobbling plugin .

I saw that VLC supports plugin and extensions (which are very simple Lua scripts) but I haven't been able to find any information on how to do this.

I guess I'd need to write a plugin that registers some callbacks -- am I right? Any idea on how I could accomplish this? It seems to be quite an uphill battle figuring this out. Can I do this using Python?

approxiblue
  • 6,982
  • 16
  • 51
  • 59
Mridang Agarwalla
  • 43,201
  • 71
  • 221
  • 382

2 Answers2

14

I'm using VLC 2.2.1 on Windows.

Here's a simple Lua plugin that recognizes play/pause/stop events:

function descriptor()
  return {
    title = "VLC Dummy Extension",
    capabilities = { "playing-listener" }
  }
end

function activate()
end

function deactivate()
end

function meta_changed()
end

function playing_changed()
  vlc.msg.dbg("[Dummy] Status: " .. vlc.playlist.status())
end

Notes:

  • VLC will call activate(), deactivate(), meta_changed() at some point in the plugin life cycle. You're not required to include them, but VLC will fill up your debug log with useless "function not found" messages.
  • If the plugin's capabilities contain playing-listener, VLC will expect the playing_changed() hook and call it when appropriate (even though the code comment says the hook name is "status_changed").
  • vlc.playlist.status() returns "stopped", "playing", "paused" or "unknown".

Run:

  • Save the plugin in a .lua file, then drop it in VLC's extensions folder: %APPDATA%\vlc\lua\extensions\ (Windows) or ~/.local/share/vlc/lua/extensions/ (Linux).
  • Load it by Tools > Plugins and extensions, Reload extensions (restarting VLC is unnecessary).
  • Activate it (there's an option under View, named after title in the plugin descriptor); activate() will be called.
  • To view all logs (vlc.msg calls), open Tools > Messages (Ctrl+M), set the level to debug and filter by "lua".

To do something whenever a new item plays:

  • Add input-listener to the plugin's capabilities.
  • Add the corresponding hook input_changed().
  • Use vlc.input.item() to get the current item (name, URI, metadata, etc).
  • You can post what's playing to an HTTP server. VLC offers vlc.net, which means you have to program for sockets. Fortunately, the extension vlsub, shipped with VLC by default, has utility methods for sending GET requests. We can steal those.

In this example, I'm just sending a threadbare GET:

http://127.0.0.1:5000/?name=MMFR.2015.720p.mp4

If I don't care about redirects or reading responses, get() is a lot simpler than the vlsub's version:

function descriptor()
  return {
    title = "VLC Dummy Extension",
    capabilities = { "input-listener" }
  }
end

function activate()
end

function deactivate()
end

function meta_changed()
end

function input_changed()
  if vlc.input.is_playing() and vlc.playlist.status() == "playing" then
    local item = vlc.input.item()
    if item then
      vlc.msg.dbg("[Dummy] Now playing: " .. item:name())
      get("http://127.0.0.1:5000/?name=" .. item:name())
    end
  end
end

function get(url)
  local u = vlc.net.url_parse(url)
  local host, port, path = u["host"], u["port"], u["path"]
  local header = {
    "GET "..path.." HTTP/1.1",
    "Host: "..host,
    "",
    ""
  }
  local request = table.concat(header, "\r\n")
  http_req(host, port, request)
end

function http_req(host, port, request)
  local fd = vlc.net.connect_tcp(host, port)
  if not fd then return false end
  local pollfds = {}

  pollfds[fd] = vlc.net.POLLIN
  vlc.net.send(fd, request)
  vlc.net.poll(pollfds)

  local chunk = vlc.net.recv(fd, 2048)
  while chunk do
    vlc.net.poll(pollfds)
    chunk = vlc.net.recv(fd, 1024)
  end
  vlc.net.close(fd)
end
approxiblue
  • 6,982
  • 16
  • 51
  • 59
  • Awesome answer! Thank you! Is there any way to run these extensions with VLC on the command line? I've read that it isn't possible to load an extension from the command line (http://stackoverflow.com/questions/29047899/run-vlc-extension-from-command-line), but I'm wondering if there's a way to do it roundabout... load an *interface* (which is loadable via `-I luaintf --lua-intf="someinterface"`) which then loads the extension that reports metadata via HTTP? Also, is there a list of the fields available on `item`? I really need the URI. – Brad Dec 13 '15 at 22:49
  • @Brad You can get the URI: `item::uri()`. Here's the list of all methods on [`vlc.input.item()`](https://github.com/videolan/vlc/blob/2.2.0-git/share/lua/README.txt#L107). Looking into interfaces... – approxiblue Dec 13 '15 at 23:03
  • 2
    @Brad You can't unfortunately. To load an extension, VLC has to set up a thread, an event loop, then register Lua hooks so they get updates from the VLC core. This happens in C. When loading an interface, VLC doesn't do any of that, and you can't from a Lua interface script either. – approxiblue Dec 14 '15 at 00:41
  • @Brad An interface is passive from what I've seen. You won't get events from VLC core. There used to be `vlc.var.add_callback` and `vlc.var.del_callback` (2.0+ versions) which let you listen to any core events from an extension, but they were [deemed unstable](https://mailman.videolan.org/pipermail/vlc-devel/2012-March/087600.html) and removed in later versions. Some [old addons are affected](http://addons.videolan.org/content/show.php/Time?content=149618) (logs in comments say these functions disappear in 2.1). – approxiblue Dec 14 '15 at 00:46
  • Thank you very much for this information. I'll award the bounty as soon as Stack Overflow lets me. For my use case, I need to run `cvlc` and have VLC hit an HTTP server with track updates. I think that since I cannot load an extension directly, I'll have to use the metadata fetching Lua script method. I have this working... it's hacky but sort of works. Thanks again for all of your help! It's too bad the scrobbler URL config parameter on the last.fm plugin doesn't work correctly... I could just use that directly. – Brad Dec 14 '15 at 00:54
  • @Brad Thanks. Is your metadata fetching script an extension, an interface or something else? – approxiblue Dec 14 '15 at 00:57
  • 1
    I can't use an extension because there is seemingly no way to load it on the command line. I was hoping to make an interface which I can easily load on the command line, but there is seemingly no way to get events for when the track changes using the interface API. (Please correct me if I'm wrong on that.) So what I've done is put a script in `lua/meta/fetcher` that implements `fetch_ meta()`. When a new track is played, this is called and I can infer in my script that a track is playing. (Hopefully VLC never tries to use it to get metadata at other times.) – Brad Dec 14 '15 at 01:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/97795/discussion-between-approxiblue-and-brad). – approxiblue Dec 14 '15 at 01:06
2

You have two options: write a lua module (check the MSN notification plugin for sample code) or write a C plugin (similar to the scrobbler you already found).

I'd suggest the lua way, since it's cross-platform compatible and it's a way easier language. Additionally, compiling C plugins for VLC is a real pain unless you use Linux or OS X.

Python is only supported for client apps on top of VLC at the moment, but we don't support its use internal to VLC.

feepk
  • 1,756
  • 1
  • 12
  • 14
  • Could you by chance provide a more full example of the Lua module or provide links to the code in question? I've added a bounty to this question, and since you're most of the way there in fully answering the question, I'd like to award it to you if you can provide more information. – Brad Dec 13 '15 at 03:19