diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/downloaders.js | 14 | ||||
-rw-r--r-- | src/loop-play.js | 56 | ||||
-rwxr-xr-x | src/play.js | 27 |
3 files changed, 88 insertions, 9 deletions
diff --git a/src/downloaders.js b/src/downloaders.js index 04838c2..c3dc43d 100644 --- a/src/downloaders.js +++ b/src/downloaders.js @@ -102,11 +102,25 @@ function makePowerfulDownloader(downloader, maxAttempts = 5) { } } +function makeConverterDownloader(downloader, type) { + return async function(arg) { + const inFile = await downloader(arg) + const base = path.basename(inFile, path.extname(inFile)) + const tempDir = tempy.directory() + const outFile = tempDir + base + '.' + type + + await promisifyProcess(spawn('avconv', ['-i', inFile, outFile]), false) + + return outFile + } +} + module.exports = { makeHTTPDownloader, makeYouTubeDownloader, makeLocalDownloader, makePowerfulDownloader, + makeConverterDownloader, getDownloaderFor(arg) { if (arg.startsWith('http://') || arg.startsWith('https://')) { diff --git a/src/loop-play.js b/src/loop-play.js index 8fdbdf3..b0bb4dd 100644 --- a/src/loop-play.js +++ b/src/loop-play.js @@ -5,8 +5,9 @@ const { spawn } = require('child_process') const FIFO = require('fifo-js') const EventEmitter = require('events') -const { getDownloaderFor } = require('./downloaders') +const { getDownloaderFor, makeConverterDownloader } = require('./downloaders') const { getItemPathString } = require('./playlist-utils') +const promisifyProcess = require('./promisify-process') class DownloadController extends EventEmitter { waitForDownload() { @@ -65,11 +66,12 @@ class DownloadController extends EventEmitter { class PlayController { constructor(picker, downloadController) { - this.currentTrack = null - this.playOpts = [] - this.process = null this.picker = picker this.downloadController = downloadController + this.playOpts = [] + this.playerCommand = null + this.currentTrack = null + this.process = null } async loopPlay() { @@ -112,13 +114,47 @@ class PlayController { if (picked === null) { return null } else { - const downloader = getDownloaderFor(picked.downloaderArg) + let downloader = getDownloaderFor(picked.downloaderArg) + downloader = makeConverterDownloader(downloader, 'wav') this.downloadController.download(downloader, picked.downloaderArg) return picked } } playFile(file) { + if (this.playerCommand === 'sox' || this.playerCommand === 'play') { + return this.playFileSoX(file) + } else if (this.playerCommand === 'mpv') { + return this.playFileMPV(file) + } else { + if (this.playerCommand) { + console.warn('Invalid player command given?', this.playerCommand) + } else { + console.warn('No player command given?') + } + + return Promise.resolve() + } + } + + playFileSoX(file) { + // SoX's play command is useful for systems that don't have MPV. SoX is + // much easier to install (and probably more commonly installed, as well). + // You don't get keyboard controls such as seeking or volume adjusting + // with SoX, though. + + this.process = spawn('play', [ + ...this.playOpts, + file + ]) + + return promisifyProcess(this.process) + } + + playFileMPV(file) { + // The more powerful MPV player. MPV is virtually impossible for a human + // being to install; if you're having trouble with it, try the SoX player. + this.fifo = new FIFO() this.process = spawn('mpv', [ '--input-file=' + this.fifo.path, @@ -194,7 +230,7 @@ class PlayController { } sendCommand(command) { - if (this.fifo) { + if (this.playerCommand === 'mpv' && this.fifo) { this.fifo.write(command) } } @@ -235,15 +271,19 @@ class PlayController { } } -module.exports = function loopPlay(picker, playOpts = []) { +module.exports = function loopPlay( + picker, playerCommand = 'mpv', playOpts = [] +) { // Looping play function. Takes one argument, the "picker" function, // which returns a track to play. Stops when the result of the picker // function is null (or similar). Optionally takes a second argument // used as arguments to the `play` process (before the file name). const downloadController = new DownloadController() + const playController = new PlayController(picker, downloadController) - playController.playOpts = playOpts + + Object.assign(playController, {playerCommand, playOpts}) const promise = playController.loopPlay() diff --git a/src/play.js b/src/play.js index 9515d97..6db8e68 100755 --- a/src/play.js +++ b/src/play.js @@ -6,6 +6,7 @@ const { promisify } = require('util') const clone = require('clone') const fs = require('fs') const fetch = require('node-fetch') +const commandExists = require('command-exists') const pickers = require('./pickers') const loopPlay = require('./loop-play') const processArgv = require('./process-argv') @@ -39,11 +40,22 @@ function clearConsoleLine() { process.stdout.write('\x1b[1K\r') } +async function determineDefaultPlayer() { + if (await commandExists('mpv')) { + return 'mpv' + } else if (await commandExists('play')) { + return 'play' + } else { + return null + } +} + async function main(args) { let sourcePlaylist = null let activePlaylist = null let pickerType = 'shuffle' + let playerCommand = await determineDefaultPlayer() let playOpts = [] // WILL play says whether the user has forced playback via an argument. @@ -270,6 +282,17 @@ async function main(args) { '-selector': util => util.alias('-picker'), + '-player': function(util) { + // --player <player> + // Sets the shell command by which audio is played. + // Valid options include 'sox' (or 'play') and 'mpv'. Use whichever is + // installed on your system; mpv is the default. + + playerCommand = util.nextArg() + }, + + '-player': util => util.alias('-player-command'), + '-play-opts': function(util) { // --play-opts <opts> // Sets command line options passed to the `play` command. @@ -301,11 +324,13 @@ async function main(args) { return } + console.log(`Using ${playerCommand} player.`) + const { promise: playPromise, playController: play, downloadController - } = loopPlay(picker, playOpts) + } = loopPlay(picker, playerCommand, playOpts) // We're looking to gather standard input one keystroke at a time. // But that isn't *always* possible, e.g. when piping into the http-music |