Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use LibVLC via WebChimera.js instead of VLC through its HTTP API #20

Merged
merged 20 commits into from
Oct 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac3531a
Add webchimera.js to dependencies - we're now switching!
icedream Oct 26, 2015
394b16a
We're now completely getting rid of vlc-api.
icedream Oct 27, 2015
e923f80
Merge branch 'develop' into impl/webchimera
icedream Oct 27, 2015
8520523
VLC event members are actually properties, not functions.
icedream Oct 27, 2015
8ab5244
Fix non-numeric volume value being passed through to VLC as NaN.
icedream Oct 27, 2015
824b4b9
Implement "prev"/"previous" commands.
icedream Oct 27, 2015
4eed972
Implement "empty"/"clear" commands.
icedream Oct 27, 2015
cc783d0
Implement "list"/"playlist" commands.
icedream Oct 27, 2015
ab18c3c
According to the WebChimera documentation this is not zero-based...
icedream Oct 27, 2015
d61d85e
Got the right CoffeeScript syntax for this loop now.
icedream Oct 27, 2015
eb00dc2
While we're on it, fine-tuning the playlist message.
icedream Oct 27, 2015
0762d6d
Fix line breaks and tabs not being properly escaped in ts3query.iced.
icedream Oct 27, 2015
d827211
Implement "loop" command.
icedream Oct 27, 2015
18b7d82
Implement "stop-after" command.
icedream Oct 27, 2015
7966bfb
Playlist display (command "list"/"playlist") can generate too long me…
icedream Oct 27, 2015
b238a65
This indenting stuff still sometimes doesn't really want to work out …
icedream Oct 27, 2015
a5a8ae1
Implement some easy checks for "next" and "prev" commands.
icedream Oct 27, 2015
9417e33
Small fix for "prev" not giving an error when in empty playlist.
icedream Oct 27, 2015
4d25c2b
Small fixes for empty playlists.
icedream Oct 27, 2015
f315785
Completely remove handler for vlc.onEndReached.
icedream Oct 27, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 74 additions & 48 deletions app.iced
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,33 @@ await services.find("pulseaudio").start defer err
if err
log.warn "PulseAudio could not start up, audio may not act as expected!"

# VLC HTTP API
await services.find("vlc").start defer err
# VLC via WebChimera.js
vlcService = services.find("vlc")
await vlcService.start defer err, vlc
if err
log.warn "VLC could not start up!"
await module.exports.shutdown defer()
process.exit 1
vlc = services.find("vlc").instance

# Cached information for tracks in playlist
vlcMediaInfo = {}

# TeamSpeak3
ts3clientService = services.find("ts3client")

ts3clientService.on "started", (ts3proc) =>
ts3query = ts3clientService.query

# VLC event handling
vlc.onPlaying = () =>
info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl]
ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info.originalUrl}]#{info.title}[/URL]."
vlc.onPaused = () => ts3query.sendtextmessage 2, 0, "Paused."
vlc.onForward = () => ts3query.sendtextmessage 2, 0, "Fast-forwarding..."
vlc.onBackward = () => ts3query.sendtextmessage 2, 0, "Rewinding..."
vlc.onEncounteredError = () => log.error "VLC has encountered an error! You will need to restart the bot.", arguments
vlc.onStopped = () => ts3query.sendtextmessage 2, 0, "Stopped."

ts3query.currentScHandlerID = 1
ts3query.mydata = {}

Expand Down Expand Up @@ -145,15 +158,16 @@ ts3clientService.on "started", (ts3proc) =>

switch name.toLowerCase()
when "pause"
vlc.status.pause()
# now we can toggle-pause playback this easily! yay!
vlc.togglePause()
return
when "play"
inputBB = paramline.trim()
input = (removeBB paramline).trim()

# we gonna interpret play without a url as an attempt to unpause the current song
if input.length <= 0
vlc.status.resume()
vlc.play()
return

# only allow playback from file if it's a preconfigured alias
Expand All @@ -168,11 +182,7 @@ ts3clientService.on "started", (ts3proc) =>

# TODO: permission system to check if uid is allowed to play this url or alias

await vlc.status.empty defer(err)
if err
log.warn "Couldn't empty VLC playlist", err
ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later."
return
vlc.playlist.clear()

# let's give youtube-dl a shot!
await youtubedl.getInfo input, [
Expand All @@ -189,24 +199,51 @@ ts3clientService.on "started", (ts3proc) =>
if not info.url?
info.url = input
info.title = input # URL as title

await vlc.status.play info.url, defer(err)
if err
vlc.status.empty()
log.warn "VLC API returned an error when trying to play", err
ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?"
return

ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]."
info.originalUrl = input
vlcMediaInfo[info.url] = info

# play it in VLC
vlc.play info.url
when "stop-after"
vlc.playlist.mode = vlc.playlist.Single
ts3query.sendtextmessage args.targetmode, invoker.id, "Playback will stop after the current playlist item."
when "loop"
inputBB = paramline
input = null
switch (removeBB paramline).toLowerCase().trim()
when ""
# just show current mode
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is #{if vlc.playlist.mode == vlc.playlist.Loop then "on" else "off"}."
when "on"
# enable looping
vlc.playlist.mode = vlc.playlist.Loop
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now on."
when "off"
# disable looping
vlc.playlist.mode = vlc.playlist.Normal
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now off."
else
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} on|off[/B] - Turns playlist looping on or off"
return
when "next"
await vlc.status.next defer(err)
if err
vlc.status.empty()
log.warn "VLC API returned an error when trying to skip current song", err
ts3query.sendtextmessage args.targetmode, invoker.id, "This didn't work. Does the playlist have multiple songs?"
if vlc.playlist.items.count == 0
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
return

ts3query.sendtextmessage args.targetmode, invoker.id, "Playing next song in the playlist."
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem == vlc.playlist.items.count - 1
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to next playlist item, this is the last one!"
return
vlc.playlist.next()
when "prev", "previous"
if vlc.playlist.items.count == 0
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
return
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem <= 0
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to previous playlist item, this is the first one!"
return
vlc.playlist.prev()
when "empty", "clear"
vlc.playlist.clear()
ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist."
when "enqueue", "add", "append"
inputBB = paramline.trim()
input = (removeBB paramline).trim()
Expand Down Expand Up @@ -242,36 +279,25 @@ ts3clientService.on "started", (ts3proc) =>
if not info.url?
info.url = input
info.title = input # URL as title
info.originalUrl = input
vlcMediaInfo[info.url] = info

await vlc.status.enqueue info.url, defer(err)
if err
vlc.status.empty()
log.warn "VLC API returned an error when trying to play", err
ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?"
return

# add it in VLC
vlc.playlist.add info.url
ts3query.sendtextmessage args.targetmode, invoker.id, "Added [URL=#{input}]#{info.title}[/URL] to the playlist."
when "stop"
await vlc.status.stop defer(err)

vlc.status.empty()

ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback."
# TODO: Do we need to make sure that vlc.playlist.mode is not set to "Single" here or is that handled automatically?
when "stop"
vlc.stop()
when "vol"
vol = parseInt paramline

if paramline.trim().length <= 0 or vol > 511 or vol < 0
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol <number>[/B] - takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup."
return

await vlc.status.volume paramline, defer(err)

if err
log.warn "Failed to set volume", err
ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out."
if paramline.trim().length <= 0 or vol == NaN or vol > 200 or vol < 0
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol <number>[/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup."
return

ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set."
vlc.audio.volume = vol
ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%."
when "changenick"
nick = if paramline.length > params[0].length then paramline else params[0]
if nick.length < 3 or nick.length > 30
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"string.prototype.startswith": "^0.2.0",
"sync": "^0.2.5",
"valid-url": "^1.0.9",
"vlc-api": "0.0.0",
"webchimera.js": "^0.1.38",
"which": "^1.1.2",
"winston": "^1.0.1",
"xvfb": "git://github.com/icedream/node-xvfb.git",
Expand Down
83 changes: 19 additions & 64 deletions services/vlc.iced
Original file line number Diff line number Diff line change
@@ -1,88 +1,43 @@
spawn = require("child_process").spawn
services = require("../services")
config = require("../config")
VLCApi = require("vlc-api")
wc = require("webchimera.js")
StreamSplitter = require("stream-splitter")
require_bin = require("../require_bin")

vlcBinPath = require_bin "vlc"

module.exports = class VLCService extends services.Service
dependencies: [
"pulseaudio"
]
constructor: -> super "VLC",
###
# Starts an instance of VLC and keeps it ready for service.
###
start: (cb) ->
if @_process
cb? null, @_process
if @_instance
cb? null, @_instance
return

calledCallback = false

proc = null
doStart = null
doStart = () =>
await services.find("pulseaudio").start defer(err)
if err
throw new Error "Dependency pulseaudio failed."

proc = spawn vlcBinPath, [
"-I", "http",
"--http-host", config.get("vlc-host"),
"--http-port", config.get("vlc-port"),
"--http-password", config.get("vlc-password")
"--aout", "pulse",
"--volume", "128", # 50% volume
"--no-video"
],
stdio: ['ignore', 'pipe', 'pipe']
detached: true

# logging
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
stdoutTokenizer.encoding = "utf8";
stdoutTokenizer.on "token", (token) =>
token = token.trim() # get rid of \r
@log.debug token

stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
stderrTokenizer.encoding = "utf8";
stderrTokenizer.on "token", (token) =>
token = token.trim() # get rid of \r
@log.debug token

proc.on "exit", () =>
if @state == "stopping"
return
if not calledCallback
calledCallback = true
@log.warn "VLC terminated unexpectedly during startup."
cb? new Error "VLC terminated unexpectedly."
@log.warn "VLC terminated unexpectedly, restarting."
doStart()
instance = wc.createPlayer [
"--aout", "pulse",
"--no-video"
]
instance.audio.volume = 50

@_process = proc

doStart()

setTimeout (() =>
if not calledCallback
calledCallback = true

@instance = new VLCApi
host: ":#{encodeURIComponent config.get("vlc-password")}@#{config.get("vlc-host")}",
port: config.get("vlc-port")
cb? null, @instance), 1500 # TODO: Use some more stable condition
@_instance = instance
cb? null, @_instance

###
# Shuts down the VLC instance.
###
stop: (cb) ->
if not @_process
if not @_instance
cb?()
return

@instance = null

@_process.kill()
await @_process.once "exit", defer()
# TODO: Is there even a proper way to shut this down?
@_instance = null

cb?()

20 changes: 17 additions & 3 deletions ts3query.iced
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,23 @@ merge = require "merge"

parserLog = getLogger "parser"

escape = (value) => value.toString().replace(///\\///g, "\\\\").replace(/\//g, "\\/").replace(/\|/g, "\\|").replace(///\ ///g, "\\s")

unescape = (value) => value.toString().replace(/\\s/g, " ").replace(/\\\//g, "/").replace(/\\\|/g, "|").replace(/\\\\/g, "\\")
escape = (value) => value.toString()\
.replace(/\\/g, "\\\\")\
.replace(/\//g, "\\/")\
.replace(/\|/g, "\\|")\
.replace(/\n/g, "\\n")\
.replace(/\r/g, "\\r")\
.replace(/\t/g, "\\t")\
.replace(/\ /g, "\\s")

unescape = (value) => value.toString()\
.replace(/\\s/g, " ")\
.replace(/\\t/g, "\t")\
.replace(/\\r/g, "\r")\
.replace(/\\n/g, "\n")\
.replace(/\\\|/g, "|")\
.replace(/\\\//g, "/")\
.replace(/\\\\/g, "\\")

buildCmd = (name, namedArgs, posArgs) =>
# TODO: Add support for collected arguments (aka lists)
Expand Down