CLI: Added VLCJ bindings to play through VLC.
This commit is contained in:
parent
9689547c0d
commit
729c27bd72
@ -10,11 +10,16 @@ import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.docopt.Docopt
|
||||
import jline.console.ConsoleReader
|
||||
import java.sql.Timestamp
|
||||
import java.text.SimpleDateFormat
|
||||
import java.net.URLDecoder
|
||||
import java.util.regex.Pattern
|
||||
import java.util.regex.Matcher
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import uk.co.caprica.vlcj.discovery.NativeDiscovery
|
||||
import uk.co.caprica.vlcj.component.AudioMediaListPlayerComponent
|
||||
import uk.co.caprica.vlcj.player.MediaPlayerEventListener
|
||||
|
||||
import static com.jdbernard.util.AnsiEscapeCodeSequence.*
|
||||
|
||||
@ -98,11 +103,13 @@ Configuration:
|
||||
|
||||
/// Current play queue and selection data
|
||||
Selection currentSelection = new Selection()
|
||||
Playlist playQueue = library.save(new Playlist(
|
||||
name: "CLI Queue ${sdf.format(new Date())}"))
|
||||
Playlist playQueue
|
||||
Bookmark playBookmark
|
||||
MediaFile curMediaFile
|
||||
|
||||
/// VLCJ Player
|
||||
AudioMediaListPlayerComponent vlcj
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
def opts = new Docopt(DOC).withVersion("wdiwtlt v$VERSION").parse(args)
|
||||
@ -178,14 +185,27 @@ Configuration:
|
||||
exitErr("Could not establish a connection to the database:\n\t" +
|
||||
e.localizedMessage) }
|
||||
|
||||
// Try to discover the VLC native libraries
|
||||
def vlcj
|
||||
try {
|
||||
new NativeDiscovery().discover()
|
||||
vlcj = new AudioMediaListPlayerComponent() }
|
||||
catch (Exception e) {
|
||||
exitErr("Could not find the VLC libraries. Is VLC installed?\n\t" +
|
||||
e.localizedMessage) }
|
||||
|
||||
// Create our CLI object
|
||||
def cliInst = new CommandLineInterface(new MediaLibrary(orm, libRoot),
|
||||
System.in, System.out, givenCfg)
|
||||
cliInst.repl()
|
||||
try {
|
||||
def cliInst = new CommandLineInterface(new MediaLibrary(orm, libRoot),
|
||||
vlcj, System.in, System.out, givenCfg)
|
||||
cliInst.repl() }
|
||||
catch (Exception e) { e.printStackTrace(); exitErr(e.localizedMessage) }
|
||||
finally { if (vlcj) vlcj.release() }
|
||||
}
|
||||
|
||||
public CommandLineInterface(MediaLibrary library, InputStream sin,
|
||||
OutputStream out, Properties cliConfig) {
|
||||
public CommandLineInterface(MediaLibrary library,
|
||||
AudioMediaListPlayerComponent vlcj, InputStream sin, OutputStream out,
|
||||
Properties cliConfig) {
|
||||
this.cliConfig = cliConfig
|
||||
this.library = library
|
||||
this.inStream = sin
|
||||
@ -195,7 +215,32 @@ Configuration:
|
||||
|
||||
setupTextStyles()
|
||||
|
||||
library.clean()
|
||||
|
||||
playQueue = library.save(new Playlist(
|
||||
name: "CLI Queue ${sdf.format(new Date())}"))
|
||||
|
||||
String user = System.getProperty('user.name')
|
||||
String os = System.getProperty('os.name')
|
||||
def bookmarks = library.getBookmarkByName("Last played for $user on $os.")
|
||||
if (bookmarks) playBookmark = bookmarks[0]
|
||||
else playBookmark = new Bookmark(name: "Last played for $user on $os.")
|
||||
|
||||
playBookmark.playlistId = playQueue.id
|
||||
playBookmark.playIndex = 0
|
||||
playBookmark.mediaFileId = 0
|
||||
|
||||
this.vlcj = vlcj
|
||||
|
||||
def listener = new UniversalNoopImplementation()
|
||||
listener.methods.playing = this.&playing
|
||||
listener.methods.finished = this.&finished
|
||||
listener.methods.stopped = this.&stopped
|
||||
vlcj.mediaListPlayer.mediaPlayer.addMediaPlayerEventListener(
|
||||
listener as MediaPlayerEventListener)
|
||||
|
||||
this.consoleReaderThread = new Thread().start(this.&runReaderThread)
|
||||
|
||||
}
|
||||
|
||||
void setupTextStyles() {
|
||||
@ -239,6 +284,10 @@ Configuration:
|
||||
processInput(line.split(/\s/) as LinkedList)
|
||||
} else {
|
||||
drawLeader()
|
||||
if (curMediaFile) {
|
||||
playBookmark.playTimeMs =
|
||||
vlcj.mediaListPlayer.mediaPlayer.time
|
||||
library.save(playBookmark) }
|
||||
Thread.sleep(250)
|
||||
}
|
||||
}
|
||||
@ -256,17 +305,18 @@ Configuration:
|
||||
case 'enqueue': return processEnqueue(line)
|
||||
case 'tag': return processTag(line)
|
||||
case 'clear': return processClear(line)
|
||||
case 'play': return line.size() == 0 ?
|
||||
transport.play() :
|
||||
processPlay(line)
|
||||
case 'pause': return transport.pause()
|
||||
case 'stop': return transport.stop()
|
||||
case 'play': return processPlay(line)
|
||||
case 'pause': return processPause(line)
|
||||
case 'stop': return processStop(line)
|
||||
case 'next': return processNext(line)
|
||||
case 'prev': return processPrev(line)
|
||||
case 'ff':
|
||||
case 'fastforward': return processFastForward(line)
|
||||
case 'rw':
|
||||
case 'rwd':
|
||||
case 'rewind': return processRewind(line)
|
||||
case 'vol':
|
||||
case 'volume': return processVolume(line)
|
||||
|
||||
case 'debug':
|
||||
outStream.println(
|
||||
@ -377,9 +427,13 @@ Configuration:
|
||||
selection.selectedFiles.collect { it.id }) }
|
||||
|
||||
private def processEnqueue(LinkedList line) {
|
||||
Selection selection = processSelect(line)
|
||||
return library.addToPlaylist(playQueue.id,
|
||||
selection.selectedFiles.collect { it.id }) }
|
||||
def selectedFiles = processSelect(line).selectedFiles
|
||||
selectedFiles.each {
|
||||
vlcj.mediaList.addMedia(
|
||||
new File(library.libraryRoot, it.filePath).canonicalPath) }
|
||||
playQueue = library.addToPlaylist(playQueue.id,
|
||||
selectedFiles.collect { it.id })
|
||||
return playQueue }
|
||||
|
||||
private def processTag(LinkedList line) {
|
||||
List<String> parts = line.join(' ').split(' as ').collect { it.trim() }
|
||||
@ -531,6 +585,183 @@ Configuration:
|
||||
return list
|
||||
}
|
||||
|
||||
private def processPlay(LinkedList line) {
|
||||
|
||||
def option = line.poll()
|
||||
if(option) switch(option) {
|
||||
case 'playlist':
|
||||
Playlist p = ensureExactlyOne(
|
||||
library.getByIdOrName(Playlist, line.join(' ')))
|
||||
|
||||
if (!p) { setErr('No matching playlist found.'); return }
|
||||
setPlayQueue(p)
|
||||
break
|
||||
|
||||
case 'bookmark':
|
||||
Bookmark b = ensureExactlyOne(
|
||||
library.getByIdOrName(Bookmark, line.join(' ')))
|
||||
if (!b) { setErr('No matching bookmark found.'); return }
|
||||
|
||||
Playlist p = library.getPlaylistById(b.playlistId)
|
||||
if (!p) {
|
||||
setErr('The playlist no longer exists for this bookmark')
|
||||
return }
|
||||
|
||||
setPlayQueue(p)
|
||||
vlcj.mediaListPlayer.playItem(b.playIndex)
|
||||
|
||||
if (b.playTimeMs > 0)
|
||||
vlcj.mediaListPlayer.mediaPlayer.time = b.playTimeMs
|
||||
break
|
||||
|
||||
case 'album': case 'artist': case 'file': case 'tags':
|
||||
if (vlcj.mediaListPlayer.isPlaying())
|
||||
vlcj.mediaListPlayer.stop()
|
||||
|
||||
vlcj.mediaList.clear()
|
||||
|
||||
// If we're currently playing a user's playlist, don't change
|
||||
// it, just create a new system playlist.
|
||||
if (playQueue.userCreated) {
|
||||
playQueue = library.save(new Playlist(
|
||||
name: "CLI Queue ${sdf.format(new Date())}")) }
|
||||
else library.removeAllFromPlaylist(playQueue.id)
|
||||
|
||||
line.addFirst(option)
|
||||
processEnqueue(line)
|
||||
|
||||
default:
|
||||
printLongMessage(
|
||||
"Invalid options to the ${promptStyle}play${normalStyle}" +
|
||||
" command. Use ${promptStyle}help play${normalStyle} to see a " +
|
||||
"list of valid options.") }
|
||||
|
||||
vlcj.mediaListPlayer.play() }
|
||||
|
||||
private def processPause(LinkedList line) { vlcj.mediaListPlayer.pause() }
|
||||
|
||||
private def processStop(LinkedList line) {
|
||||
// TODO
|
||||
vlcj.mediaListPlayer.stop() }
|
||||
|
||||
private def processNext(LinkedList line) {
|
||||
def count = line.poll()
|
||||
try { count = count ? count as int : 1 }
|
||||
catch (Exception e) { setErr("$count is not a valid number"); count = 1 }
|
||||
vlcj.mediaListPlayer.stop()
|
||||
if ((playBookmark.playIndex + count) < vlcj.mediaList.size())
|
||||
vlcj.mediaListPlayer.playItem(playBookmark.playIndex + count) }
|
||||
|
||||
private def processPrev(LinkedList line) {
|
||||
def count = line.poll()
|
||||
try { count = count ? count as int : 1 }
|
||||
catch (Exception e) { setErr("$count is not a valid number"); count = 1 }
|
||||
vlcj.mediaListPlayer.stop()
|
||||
if ((playBookmark.playIndex - count) >= 0)
|
||||
vlcj.mediaListPlayer.playItem(playBookmark.playIndex - count) }
|
||||
|
||||
private def processFastForward(LinkedList line) {
|
||||
def amount = line.poll()
|
||||
def unit = line.poll()?.trim()
|
||||
|
||||
if (!amount) { amount = "10"; unit = "s" }
|
||||
if (!unit) unit = 's'
|
||||
|
||||
try { amount = amount as int }
|
||||
catch (Exception e) { setErr "$amount must be an integer."; return }
|
||||
|
||||
switch (unit) {
|
||||
case 'ms': case 'millis': case 'millisecond': case 'milliseconds':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount)
|
||||
break
|
||||
case 's': case 'sec': case 'second': case 'seconds':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount * 1000)
|
||||
break
|
||||
case 'm': case 'min': case 'minute': case 'minutes':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount * 60000)
|
||||
break
|
||||
default: setErr "$unit must be one of 'milliseconds' " +
|
||||
"(or 'millis' or 'ms'), 'seconds' (or 'sec' or 's'), or " +
|
||||
"'minutes' (or 'min' or 'm')"
|
||||
return } }
|
||||
|
||||
private def processRewind(LinkedList line) {
|
||||
def amount = line.poll()
|
||||
def unit = line.poll()?.trim()
|
||||
|
||||
if (!amount) { amount = "10"; unit = "s" }
|
||||
if (!unit) unit = 's'
|
||||
|
||||
try { amount = -(amount as int) }
|
||||
catch (Exception e) { setErr "$amount must be an integer."; return }
|
||||
|
||||
switch (unit) {
|
||||
case 'ms': case 'millis': case 'millisecond': case 'milliseconds':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount)
|
||||
break
|
||||
case 's': case 'sec': case 'second': case 'seconds':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount * 1000)
|
||||
break
|
||||
case 'm': case 'min': case 'minute': case 'minutes':
|
||||
vlcj.mediaListPlayer.mediaPlayer.skip(amount * 60000)
|
||||
break
|
||||
default: setErr "$unit must be one of 'milliseconds' " +
|
||||
"(or 'millis' or 'ms'), 'seconds' (or 'sec' or 's'), or " +
|
||||
"'minutes' (or 'min' or 'm')"
|
||||
return } }
|
||||
|
||||
private def processVolume(LinkedList line) {
|
||||
def percentage = line.poll()
|
||||
if (!percentage)
|
||||
setMsg("Volume: ${vlcj.mediaListPlayer.mediaPlayer.volume}")
|
||||
|
||||
else {
|
||||
try { percentage = Math.min(Math.max(0, percentage as int), 200) }
|
||||
catch (Exception e) {
|
||||
setErr("Volume must be and integer between 0 and 200.")
|
||||
return }
|
||||
vlcj.mediaListPlayer.mediaPlayer.volume = percentage } }
|
||||
|
||||
private void playing(def player) {
|
||||
try {
|
||||
def mediaFiles = library.getMediaFilesWhere(playlistId: playQueue.id)
|
||||
String absFilePath =
|
||||
URLDecoder.decode(vlcj.mediaListPlayer.currentMrl()[7..-1])
|
||||
|
||||
def currentIdx = mediaFiles.findIndexOf {
|
||||
def mrlPath = new File(absFilePath).canonicalPath
|
||||
def mfPath = new File(library.libraryRoot, it.filePath).canonicalPath
|
||||
return mfPath == mrlPath }
|
||||
|
||||
curMediaFile = mediaFiles[currentIdx]
|
||||
|
||||
currentlyPlaying.text = makeFullMediaFileDescription(curMediaFile)
|
||||
|
||||
playBookmark.playlistId = playQueue.id
|
||||
playBookmark.playIndex = currentIdx
|
||||
playBookmark.mediaFileId = curMediaFile.id
|
||||
|
||||
library.save(playBookmark) }
|
||||
catch (Exception e) { printLongMessage(e.printStackTrace()) } }
|
||||
|
||||
private void finished(def player) {
|
||||
curMediaFile = library.incrementPlayCount(curMediaFile)
|
||||
library.save(curMediaFile) }
|
||||
|
||||
private void stopped(def player) {
|
||||
curMediaFile = null
|
||||
currentlyPlaying.text = "No media currently playing." }
|
||||
|
||||
public void setPlayQueue(Playlist p) {
|
||||
if (vlcj.mediaListPlayer.isPlaying()) vlcj.mediaListPlayer.stop()
|
||||
playQueue = p
|
||||
|
||||
vlcj.mediaList.clear()
|
||||
|
||||
library.getMediaFilesWhere(playlistId: playQueue.id).each {
|
||||
vlcj.mediaList.addMedia(
|
||||
new File(library.libraryRoot, it.filePath).canonicalPath) } }
|
||||
|
||||
public def ensureExactlyOne(def matches) {
|
||||
if (!matches) {
|
||||
setErr("Nothing matches.");
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.jdbernard.wdiwtlt.cli
|
||||
|
||||
public class UniversalNoopImplementation {
|
||||
Map methods = [:]
|
||||
|
||||
public def methodMissing(String name, def args) {
|
||||
if (methods[name]) return methods[name](*args) } }
|
Loading…
Reference in New Issue
Block a user