diff --git a/cli/compatibleFormat.json b/cli/compatibleFormat.json index b8e366e..a4a2aaf 100644 --- a/cli/compatibleFormat.json +++ b/cli/compatibleFormat.json @@ -1,3 +1,13 @@ [ - ".mp3", ".ogg", ".opus", ".aac", ".m4a", ".wav", ".flac", ".ape", ".wv", ".oga", ".webm" + ".mp3", + ".ogg", + ".opus", + ".aac", + ".m4a", + ".wav", + ".flac", + ".ape", + ".wv", + ".oga", + ".webm" ] diff --git a/cli/daemon.js b/cli/daemon.js index 879e9cb..814aa0f 100644 --- a/cli/daemon.js +++ b/cli/daemon.js @@ -1,31 +1,33 @@ // https://wiki.unix7.org/node/daemon-sample -var child_process = require('child_process'); -var { writeFileSync } = require('fs'); - +var child_process = require("child_process"); +var { writeFileSync } = require("fs"); + function child(exe, args, env) { - var child = child_process.spawn(exe, args, { - detached: true, - stdio: ['ignore', 'ignore', 'ignore'], - env: env - }) - child.unref() - return child -} - -module.exports = function(nodeBin) { - console.log('Daemon PID :', process.pid); - console.log('Daemon PPID:', process.ppid); - console.log(`\nTo kill server, Run \"openradio-pulse -k\"`); - writeFileSync(process.env.TMPDIR + "/openradio-pulse-daemon.json", JSON.stringify({ pid: process.pid, ppid: process.ppid })); - - if (process.env.__daemon) { - return process.pid - } - process.env.__daemon = true - - var args = [].concat(process.argv) - var node = args.shift() - var env = process.env - child(node, args, env) - return process.exit() + var child = child_process.spawn(exe, args, { + detached: true, + stdio: ["ignore", "ignore", "ignore"], + env: env, + }); + child.unref(); + return child; } + +module.exports = function (nodeBin) { + console.log("Daemon PID :", process.pid); + console.log(`\nTo kill server, Run \"openradio-pulse -k\"`); + writeFileSync( + process.env.TMPDIR + "/openradio-pulse-daemon.json", + JSON.stringify({ pid: process.pid }) + ); + + if (process.env.__daemon) { + return process.pid; + } + process.env.__daemon = true; + + var args = [].concat(process.argv); + var node = args.shift(); + var env = process.env; + child(node, args, env); + return process.exit(); +}; diff --git a/cli/index.js b/cli/index.js index 4b06a27..468cde3 100755 --- a/cli/index.js +++ b/cli/index.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const Throttle = require("throttle"); -const ffmpeg = require('prism-media').FFmpeg; +const ffmpeg = require("prism-media").FFmpeg; const compatibleFormat = require("./compatibleFormat.json"); const http = require("http"); const readline = require("readline"); @@ -21,54 +21,82 @@ let random = false; process.title = "OpenRadio"; process.argv.forEach((e, index) => { - if (e === "-a" || e === "--address" && index < process.argv.length - 1) { - host = process.argv[index + 1]; - } + if (e === "-a" || (e === "--address" && index < process.argv.length - 1)) { + host = process.argv[index + 1]; + } }); if (!dirname.endsWith("/")) dirname = dirname + "/"; const server = http - .createServer((req, res) => { - // if There's no stream, Do nothing and act nothing. - if (!stream) play(); - let generateId = () => Math.random().toString(36).slice(2); - let id = generateId() + generateId() + generateId() + generateId() + generateId() + generateId(); - res.setHeader("Content-Type", "audio/mpeg"); - logs.push(`[${Date()}] New Sink Connected (${id})`); - sink.set(id, res); - req.on("close", () => { - logs.push(`[${Date()}] Sink (${id}) Disconnected.`); - sink.delete(id); - }); - }) - .listen(port, host || "0.0.0.0", () => { - console.log("---> Radio started at port:", port); - console.log(`---> Send request to http://${host||"0.0.0.0"}:${port} to Start radio`); - console.log("---> Or type command to manage radio"); - console.log("---> Or Press Enter to Play the radio stadion in Background."); - console.log("\nFor command list, Type `help`"); - rl.prompt(); + .createServer((req, res) => { + // if There's no stream, Do nothing and act nothing. + if (!stream) play(); + let generateId = () => Math.random().toString(36).slice(2); + let id = + generateId() + + generateId() + + generateId() + + generateId() + + generateId() + + generateId(); + res.setHeader("Content-Type", "audio/mpeg"); + logs.push(`[${Date()}] New Sink Connected (${id})`); + sink.set(id, res); + req.on("close", () => { + logs.push(`[${Date()}] Sink (${id}) Disconnected.`); + sink.delete(id); }); + }) + .listen(port, host || "0.0.0.0", () => { + console.log("---> Radio started at port:", port); + console.log( + `---> Send request to http://${host || "0.0.0.0"}:${port} to Start radio` + ); + console.log("---> Or type command to manage radio"); + console.log("---> Or Press Enter to Play the radio stadion in Background."); + console.log("\nFor command list, Type `help`"); + rl.prompt(); + }); const Fs = require("fs"); const { extname } = require("path"); const _readDir = () => Fs.readdirSync(dirname, { withFileTypes: true }); -const _isAudio = item => (item.isFile && compatibleFormat.includes(extname(item.name))); +const _isAudio = (item) => + item.isFile && compatibleFormat.includes(extname(item.name)); let manager = {}; function convert(filename) { - return new ffmpeg({ - args: ["-i", filename, "-analyzeduration", "0", "-loglevel", "0", "-f", "mp3", "-ar", "48000", "-ac", "2", "-ab", "192k", "-map", "0:a", "-map_metadata", "-1"], - }); + return new ffmpeg({ + args: [ + "-i", + filename, + "-analyzeduration", + "0", + "-loglevel", + "0", + "-f", + "mp3", + "-ar", + "48000", + "-ac", + "2", + "-ab", + "192k", + "-map", + "0:a", + "-map_metadata", + "-1", + ], + }); } manager.readSong = () => _readDir().filter(_isAudio)[0].name; manager.readSongs = () => - _readDir() - .filter(_isAudio) - .map((songItem) => songItem.name); + _readDir() + .filter(_isAudio) + .map((songItem) => songItem.name); manager.discardFirstWord = (str) => str.substring(str.indexOf(" ") + 1); manager.getFirstWord = (str) => str.split(" ")[0]; @@ -76,201 +104,208 @@ manager.getFirstWord = (str) => str.split(" ")[0]; console.log("Total Songs in Directory:", manager.readSongs().length); async function play(n) { - let song = manager.readSongs(); - if (manager.readSongs().length === 0) { - console.log("There's no songs in this directory. Please put one and try again!"); - return rl.prompt(); - } - if (stream) { - if (stream.playing) return; - } - let filename = song[new Number(n) - 1] || song[songnum++]; - if (n) songnum = n; + let song = manager.readSongs(); + if (manager.readSongs().length === 0) { + console.log( + "There's no songs in this directory. Please put one and try again!" + ); + return rl.prompt(); + } + if (stream) { + if (stream.playing) return; + } + let filename = song[new Number(n) - 1] || song[songnum++]; + if (n) songnum = n; - if (loop) { - if (!n) { - if (songnum != 1) { - songnum = songnum - 1; - filename = song[songnum - 1]; - } else { - songnum = manager.readSongs().length; - filename = song[songnum - 1]; - } - } + if (loop) { + if (!n) { + if (songnum != 1) { + songnum = songnum - 1; + filename = song[songnum - 1]; + } else { + songnum = manager.readSongs().length; + filename = song[songnum - 1]; + } } - - if (random) { - if (!n) { - songnum = Math.floor(Math.random() * song.length); - filename = song[songnum - 1]; - } + } + + if (random) { + if (!n) { + songnum = Math.floor(Math.random() * song.length); + filename = song[songnum - 1]; } - - if (!filename || !songnum) { - filename = song[0]; - songnum = 1; - } - - stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); - stream.on("data", (chunk) => { - sink.forEach((s) => { - s.write(chunk); - }); - }); - stream.playing = true; - stream.on("end", () => { - if (!stream) return; - if (stream.stopped) return; - stream.playing = false; - play(); - }); + } + + if (!filename || !songnum) { + filename = song[0]; + songnum = 1; + } + + stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); + stream.on("data", (chunk) => { + sink.forEach((s) => { + s.write(chunk); + }); + }); + stream.playing = true; + stream.on("end", () => { + if (!stream) return; + if (stream.stopped) return; + stream.playing = false; + play(); + }); - stream.on('error', err => { - console.error(`An error occured when playing ${filename}:`, err); - console.log("Skipping...."); - play(); - }); + stream.on("error", (err) => { + console.error(`An error occured when playing ${filename}:`, err); + console.log("Skipping...."); + play(); + }); - console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); - np = filename; - rl.prompt(); + console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); + np = filename; + rl.prompt(); } -rl.on('line', str => { - if (str) { - let command = (str.split(" ")[0] || "").toLowerCase(); - if (command === "help") { - console.log("skip -", "Skip & Play other song"); - console.log("np -", "Showing Current playing song name"); - console.log("q / ls -", "Showing song name in current folder"); - console.log("p -", "Skip & play provided song number"); - console.log("stop -", "Stop the player"); - console.log("logs -", "Show HTTP Traffic Logs"); - console.log("clearlogs -", "Clear logs"); - console.log("sink -", "Show all Sink name"); - console.log("loop -", "Loop the current song"); - console.log("random -", "Enable Random song fetching"); - console.log("pause -", "Pause the radio"); - console.log("resume -", "Resume the radio"); - console.log("cd -", "Change directory"); - console.log("\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`"); - } else if (command === "skip") { - if (!stream) return console.log("Nothing Playing."); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - return play(); - } else if (command === "np") { - if (!np) return console.log("Nothing Playing."); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } else if (command === "q" || command === "ls") { - console.log("############### Song List ###############\n"); - let cl = 1; - manager.readSongs().forEach((e) => { - console.log(`[${cl}]`, e); - cl++; - }); - if (np) { - console.log(""); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } - } else if (command === "p" || command === "play") { - let songnumber = Number(str.split(" ").slice(1)[0]); - if (!songnumber) { - console.log("Usage: p [Song number]"); - console.log("To get song number, Do `q`\n"); - return rl.prompt(); - } - let sname = manager.readSongs()[songnumber - 1]; - if (!sname) { - console.log("Song not found\n"); - return rl.prompt(); - } - if (!stream) return play(songnumber); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - play(songnumber); - return; - } else if (command === "stop") { - if (!stream) return console.log('Nothing playing.'); - stream.stopped = true; - stream.playing = false; - np = null; - stream.end(); - } else if (command === "logs") { - console.log(logs.join("\n")); - } else if (command === "clearlogs") { - logs = []; - } else if (command === "sink") { - a = 1; - let args = str.split(" ").slice(1).join(" "); - if (!args) { - console.log("--------------------- Sink Manager -"); - console.log("--- ID -----------------------------"); - sink.forEach((res, name) => { - console.log(`${a}. ${name}`); - a++; - }); - console.log("------------------------------------\nTotal Sink:", sink.size); - console.log("To remove sink ID, Execute `.sink remove `"); - } else { - if (args.startsWith("remove")) { - let id = args.split(" ").slice(1).join(" "); - if (!id) { - console.log("Usage: .sink remove "); - } else { - let res = sink.get(id); - if (!res) { - console.log("There's no Sink ID", res); - } else { - res.end(); - sink.delete(id); - console.log("--> Removed Sink", id); - console.log("Total Sink:", sink.size); - } - } - } - } - } else if (command === "loop") { - if (random) random = false; - if (!loop) { - loop = true; - console.log("Loop is now enabled."); - } else { - loop = false; - console.log("Loop is now disabled"); - } - } else if (command === "random") { - if (loop) loop = false; - if (!random) { - random = true; - console.log("Random mode is now enabled"); +rl.on("line", (str) => { + if (str) { + let command = (str.split(" ")[0] || "").toLowerCase(); + if (command === "help") { + console.log("skip -", "Skip & Play other song"); + console.log("np -", "Showing Current playing song name"); + console.log("q / ls -", "Showing song name in current folder"); + console.log("p -", "Skip & play provided song number"); + console.log("stop -", "Stop the player"); + console.log("logs -", "Show HTTP Traffic Logs"); + console.log("clearlogs -", "Clear logs"); + console.log("sink -", "Show all Sink name"); + console.log("loop -", "Loop the current song"); + console.log("random -", "Enable Random song fetching"); + console.log("pause -", "Pause the radio"); + console.log("resume -", "Resume the radio"); + console.log("cd -", "Change directory"); + console.log( + "\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`" + ); + } else if (command === "skip") { + if (!stream) return console.log("Nothing Playing."); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + return play(); + } else if (command === "np") { + if (!np) return console.log("Nothing Playing."); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } else if (command === "q" || command === "ls") { + console.log("############### Song List ###############\n"); + let cl = 1; + manager.readSongs().forEach((e) => { + console.log(`[${cl}]`, e); + cl++; + }); + if (np) { + console.log(""); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } + } else if (command === "p" || command === "play") { + let songnumber = Number(str.split(" ").slice(1)[0]); + if (!songnumber) { + console.log("Usage: p [Song number]"); + console.log("To get song number, Do `q`\n"); + return rl.prompt(); + } + let sname = manager.readSongs()[songnumber - 1]; + if (!sname) { + console.log("Song not found\n"); + return rl.prompt(); + } + if (!stream) return play(songnumber); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + play(songnumber); + return; + } else if (command === "stop") { + if (!stream) return console.log("Nothing playing."); + stream.stopped = true; + stream.playing = false; + np = null; + stream.end(); + } else if (command === "logs") { + console.log(logs.join("\n")); + } else if (command === "clearlogs") { + logs = []; + } else if (command === "sink") { + a = 1; + let args = str.split(" ").slice(1).join(" "); + if (!args) { + console.log("--------------------- Sink Manager -"); + console.log("--- ID -----------------------------"); + sink.forEach((res, name) => { + console.log(`${a}. ${name}`); + a++; + }); + console.log( + "------------------------------------\nTotal Sink:", + sink.size + ); + console.log("To remove sink ID, Execute `.sink remove `"); + } else { + if (args.startsWith("remove")) { + let id = args.split(" ").slice(1).join(" "); + if (!id) { + console.log("Usage: .sink remove "); + } else { + let res = sink.get(id); + if (!res) { + console.log("There's no Sink ID", res); } else { - random = false; - console.log("Random mode is now Disabled"); + res.end(); + sink.delete(id); + console.log("--> Removed Sink", id); + console.log("Total Sink:", sink.size); } - } else if (command === "pause") { - if (!stream) return console.log('Nothing Playing.'); - stream.pause(); - } else if (command === "resume") { - if (!stream) return console.log('Nothing Playing.'); - stream.resume(); - } else if (command === "cd") { - let args = str.split(" ").slice(1).join(" "); - try { - process.chdir(args || process.env.HOME); - dirname = process.cwd() + "/"; - } catch (error) { - console.error(error); - } - } - } else { - if (!stream) play(); + } + } + } + } else if (command === "loop") { + if (random) random = false; + if (!loop) { + loop = true; + console.log("Loop is now enabled."); + } else { + loop = false; + console.log("Loop is now disabled"); + } + } else if (command === "random") { + if (loop) loop = false; + if (!random) { + random = true; + console.log("Random mode is now enabled"); + } else { + random = false; + console.log("Random mode is now Disabled"); + } + } else if (command === "pause") { + if (!stream) return console.log("Nothing Playing."); + stream.pause(); + } else if (command === "resume") { + if (!stream) return console.log("Nothing Playing."); + stream.resume(); + } else if (command === "cd") { + let args = str.split(" ").slice(1).join(" "); + try { + process.chdir(args || process.env.HOME); + dirname = process.cwd() + "/"; + } catch (error) { + console.error(error); + } } - rl.prompt(); + } else { + if (!stream) play(); + } + rl.prompt(); }); rl.setPrompt("Command > "); diff --git a/cli/pulse.js b/cli/pulse.js index 5aa4df2..471842d 100755 --- a/cli/pulse.js +++ b/cli/pulse.js @@ -1,122 +1,204 @@ #!/usr/bin/env node const supportedPlatform = ["Linux"]; +const supportedFormats = ["mpegts", "adts", "mp3", "mp2", "s16le"]; const { spawn } = require("child_process"); const openradio = require("../"); -const daemon = require("./daemon"); const fs = require("fs"); const http = require("http"); const server = http.createServer(); const argv = process.argv.slice(2); const sink = new Map(); const config = { - input: { - rate: 44100, - channels: 2 - }, - output: { - bitrate: 320, - channels: 2, - rate: 48000 - }, - server: { - port: 8080, - address: "0.0.0.0" - }, - parec_path: process.env.PREFIX + "/bin/parec", - log: 0, - daemon: 1 -} + input: { + rate: 44100, + channels: 2, + }, + output: { + bitrate: 705, + channels: 2, + rate: 48000, + format: "adts", + }, + server: { + port: 8080, + address: "0.0.0.0", + }, + parec_path: process.env.PREFIX + "/bin/parec", + log: 0, + daemon: 1, + force: + argv.includes("-force") || argv.includes("--force") || argv.includes("-f"), +}; + +let warn = (text) => { + console.warn(text); + process.exit(1); +}; + +let error = (text) => { + console.error(text); + process.exit(1); +}; -if (!supportedPlatform.includes(require("os").type()) && !["--force", "-force", "-f"].includes(argv[0])) { - console.log("Sorry. But we may stop here."); - console.log(`\nYour platform (${require("os").type()}) is not supported to ran openradio-pulse. \nIf you wish to continue anyway, Simply do "openradio-pulse -f".`); - process.exit(1); +if (config.force) + console.warn("Warning: I hope you understand what are you doing."); + +if (!supportedPlatform.includes(require("os").type()) && !config.force) { + console.log("Sorry. But we may stop here."); + console.log( + `\nYour platform (${require("os").type()}) is not supported to ran openradio-pulse. \nIf you wish to continue anyway, Simply do "openradio-pulse -f".` + ); + process.exit(1); } -console.log("\nOpenradio Pulseaudio - v1.1"); +console.log("\nOpenradio Pulseaudio - v1.2 Alpha"); argv.forEach(async (key, index) => { - let value = argv[index+1]; - if (["--port", "-port", "-p"].includes(key)) { - if (isNaN(value)) return console.error("Usage: openradio-pulse --port [Port Number]"); - config.server.port = value; - } else if (["-h", "-help", "--help"].includes(key)) { - console.log("\nUsage: openradio-pulse [options]"); - console.log("\nCommon Options:\n"); - console.log(" --address [addr] - IP Address to listen http server from."); - console.log(" --port [num] - Port to listen HTTP request (Default: 8080)"); - console.log(" --parec-path [Path] - Path to parec binary (Default: $PREFIX/bin/parec)"); - console.log(" --help - Show this"); - console.log(" --log - Log every HTTP Traffic"); - console.log("\nDaemonize Service:\n"); - console.log(" --daemon - Do not run as Daemonize Service"); - console.log(" --kill - Kill Daemonize service"); - console.log("\nAudio Input Options:\n"); - console.log(" --input-samplerate [num] - Input Samplerate (Default: 44100)"); - console.log(" --input-channels [num] - Input Channels (Default: 2)"); - console.log("\nAudio Output Options:\n"); - console.log(" --output-bitrate [num] - Audio output Bitrate (Default: 320)"); - console.log(" --output-channels [num] - Audio output channels (Default: 2)"); - console.log(" --output-samplerate [num] - Audio output samplerate (Default: 48000)"); - console.log("\nTo make this works perfectly, Make sure 'parec' binary is available in your system."); - process.exit(0); - } else if (["-prp", "-parec-path", "--parec-path"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --parec-path [parec binary path]"); - config.parec_path = value; - } else if (["--input-samplerate", "-input-samplerate", ,"-ir"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --input-samplerate [num]"); - config.input.rate = value; - } else if (["--input-channels", "-input-channels", "-ic"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --input-channels [num]"); - config.input.channels = value; - } else if (["--output-bitrate", "-output-bitrate", "-ob"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --output-bitrate [num]"); - config.output.bitrate = value; - } else if (["--output-channels", "-output-channels", "-oc"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --output-channels [num]"); - config.output.channels = value; - } else if (["--output-samplerate", "-output-samplerate", "-or"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --output-samplerate [num]"); - config.output.rate = value; - } else if (["--address", "-address", "-a"].includes(key)) { - if (!value) return console.error("Usage: openradio-pulse --address [addr]"); - config.server.address = value; - } else if (["--log", "-log", "-l", "-verbose", "--verbose", "-v"].includes(key)) { - config.log = 1; - } else if (["--daemon", "-daemon", "-d"].includes(key)) { - config.daemon = 0; - } else if (["--kill", "-kill", "-k"].includes(key)) { - try { - let daemons = JSON.parse(fs.readFileSync(process.env.TMPDIR + "/openradio-pulse-daemon.json")); - console.log("Killing process " + daemons.pid + "...."); - process.kill(daemons.pid, 'SIGINT'); - fs.rmSync(process.env.TMPDIR + "/openradio-pulse-daemon.json"); - } catch (error) { - if (error.code === "ENOENT") { - console.error("No daemon was running or already killed.\nYou may should kill it manually by ran \"pkill -9 node\" if it's still running."); - return process.exit(1); - } - console.error(error); - return process.exit(1); - } - process.exit(); - } + let value = argv[index + 1]; + if (["--port", "-port", "-p"].includes(key)) { + if (isNaN(value)) + return error("Usage: openradio-pulse --port [Port Number]"); + config.server.port = value; + } else if (["-h", "-help", "--help"].includes(key)) { + console.log("\nUsage: openradio-pulse [options]"); + console.log("\nCommon Options:\n"); + console.log( + " --address [addr] - IP Address to listen http server from." + ); + console.log( + " --port [num] - Port to listen HTTP request (Default: 8080)" + ); + console.log( + " --parec-path [Path] - Path to parec binary (Default: $PREFIX/bin/parec)" + ); + console.log(" --help - Show this"); + console.log(" --log - Log every HTTP Traffic"); + console.log(" --force - Force any actions"); + console.log("\nDaemonize Service:\n"); + console.log(" --no-daemon - Do not run as Daemonize Service"); + console.log(" --kill - Kill Daemonize service"); + console.log("\nAudio Input Options:\n"); + console.log( + " --input-samplerate [num] - Input Samplerate (Default: 44100)" + ); + console.log(" --input-channels [num] - Input Channels (Default: 2)"); + console.log("\nAudio Output Options:\n"); + console.log( + " --output-bitrate [num] - Audio output Bitrate (Default: 320)" + ); + console.log( + " --output-channels [num] - Audio output channels (Default: 2)" + ); + console.log( + " --output-samplerate [num] - Audio output samplerate (Default: 48000)" + ); + console.log( + " --output-format [format] - Audio output formats (Default: mp3)" + ); + console.log( + " Supported Formats:", + supportedFormats.join(", ") + ); + console.log( + "\nTo make this works perfectly, Make sure 'parec' binary is available in your system." + ); + process.exit(0); + } else if (["-prp", "-parec-path", "--parec-path"].includes(key)) { + if (!value) + return error("Usage: openradio-pulse --parec-path [parec binary path]"); + config.parec_path = value; + } else if ( + ["--input-samplerate", "-input-samplerate", , "-ir"].includes(key) + ) { + if (!value) return error("Usage: openradio-pulse --input-samplerate [num]"); + config.input.rate = value; + } else if (["--input-channels", "-input-channels", "-ic"].includes(key)) { + if (!value) return error("Usage: openradio-pulse --input-channels [num]"); + config.input.channels = value; + } else if (["--output-bitrate", "-output-bitrate", "-ob"].includes(key)) { + if (!value) return error("Usage: openradio-pulse --output-bitrate [num]"); + config.output.bitrate = value; + } else if (["--output-channels", "-output-channels", "-oc"].includes(key)) { + if (!value) return error("Usage: openradio-pulse --output-channels [num]"); + config.output.channels = value; + } else if ( + ["--output-samplerate", "-output-samplerate", "-or"].includes(key) + ) { + if (!value) + return error("Usage: openradio-pulse --output-samplerate [num]"); + config.output.rate = value; + } else if (["--output-format", "-output-format", "-of"].includes(key)) { + if (!value) return error("Usage: openradio-pulse --output-format [format]"); + if (["list", "help"].includes(value)) + return console.log( + "Supported Formats List:", + supportedFormats.join(", ") + ); + if (!supportedFormats.includes(value) && !config.force) + return error('Unsupported Audio Output. See "openradio-pulse -of list"'); + config.output.format = value; + } else if (["--address", "-address", "-a"].includes(key)) { + if (!value) return error("Usage: openradio-pulse --address [addr]"); + config.server.address = value; + } else if ( + ["--log", "-log", "-l", "-verbose", "--verbose", "-v"].includes(key) + ) { + config.log = 1; + } else if (["--no-daemon", "-no-daemon", "-nd"].includes(key)) { + config.daemon = 0; + } else if (["--kill", "-kill", "-k"].includes(key)) { + try { + let daemons = JSON.parse( + fs.readFileSync(process.env.TMPDIR + "/openradio-pulse-daemon.json") + ); + console.log("Killing process " + daemons.pid + "...."); + try { + process.kill(daemons.pid, "SIGKILL"); + } catch (error) { + if (error.code === "ESRCH") { + console.log("Daemon is already die. Removing temporary files...."); + } else error(error); + } + fs.rmSync(process.env.TMPDIR + "/openradio-pulse-daemon.json"); + } catch (error) { + if (error.code === "ENOENT") { + console.error( + "There's no daemon was running or already killed.\nYou may should kill it manually by ran \"pkill -9 node\" if it's still running." + ); + return process.exit(1); + } + console.error(error); + return process.exit(1); + } + process.exit(); + } }); -server.on('error', err => console.error(`[${Date()}]`, err)); -server.on('request', (req, res) => { - let id = Math.random(); - let address = req.socket.address(); - res.writeHead(200, { "content-type": "audio/mp3" }); - sink.set(id, res); - if (config.log) console.log(`[${Date()}]`, "New Client:", `${address.address}:${address.port}`); - req.on('close', () => { - sink.delete(id); - if (config.log) console.log(`[${Date()}]`, "Client Disconnected:", `${address.address}:${address.port}`); - }); +server.on("error", (err) => console.error(`[${Date()}]`, err)); +server.on("request", (req, res) => { + let id = Math.random(); + let address = req.socket.address(); + res.writeHead(200, { "content-type": "audio/" + config.output.format }); + sink.set(id, res); + if (config.log) + console.log( + `[${Date()}]`, + "New Client:", + `${address.address}:${address.port}` + ); + req.on("close", () => { + sink.delete(id); + if (config.log) + console.log( + `[${Date()}]`, + "Client Disconnected:", + `${address.address}:${address.port}` + ); + }); }); -console.log("For more information, do \"openradio-pulse -h\"\n"); +console.log('For more information, do "openradio-pulse -h"\n'); + console.log("Configuration:"); console.log("- Input"); console.log(" SampleRate:", config.input.rate); @@ -125,35 +207,40 @@ console.log("\n- Output"); console.log(" SampleRate:", config.output.rate); console.log(" Channels :", config.output.channels); console.log(" Bitrate :", config.output.bitrate); +console.log(" Format :", config.output.format); console.log("\n- HTTP Server"); console.log(" Address :", config.server.address); console.log(" Port :", config.server.port); console.log("\nparec Binary path:", config.parec_path); -if (config.daemon) daemon(); +if (config.daemon) require("./daemon")(); console.log("Log Incomming Traffic:", config.log ? "Yes" : "No"); -process.stdout.write(`\n[${Date()}] Launching Server.... `); +if (config.daemon && !process.env.__daemon) return; +process.stdout.write(`\n[${Date()}] Launching Server.... `); let listener = server.listen(config.server.port, config.server.address, () => { - process.stdout.write("Done"); - console.log(`\n[${Date()}]`, "Now listening on port", listener.address().port); + process.stdout.write("Done"); + console.log( + `\n[${Date()}]`, + "Now listening on port", + listener.address().port + ); - let radio = openradio(config.output); - function play() { - let parec = spawn(config.parec_path, config.input); - parec.on('close', play); - parec.on('error', err => console.error(`[${Date()}]`, err)); - parec.stderr.pipe(process.stderr); - radio.playPCM(parec.stdout, config.input); - } - - radio.on('data', chunk => { - sink.forEach((res, id) => { - res.write(chunk, err => { - if (err) sink.delete(id); - }); - }); - }); + let radio = openradio(config.output); + function play() { + let parec = spawn(config.parec_path, config.input); + parec.on("close", play); + parec.on("error", (err) => console.error(`[${Date()}]`, err)); + parec.stderr.pipe(process.stderr); + radio.playPCM(parec.stdout, config.input); + } - play(); -}); + radio.on("data", (chunk) => { + sink.forEach((res, id) => { + res.write(chunk, (err) => { + if (err) sink.delete(id); + }); + }); + }); + play(); +}); diff --git a/cli/tcp.js b/cli/tcp.js index f74b301..a1d0ca8 100755 --- a/cli/tcp.js +++ b/cli/tcp.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const Throttle = require("throttle"); -const ffmpeg = require('prism-media').FFmpeg; +const ffmpeg = require("prism-media").FFmpeg; const compatibleFormat = require("./compatibleFormat.json"); const net = require("net"); const readline = require("readline"); @@ -21,57 +21,86 @@ let random = false; process.title = "OpenRadio"; process.argv.forEach((e, index) => { - if (e === "-a" || e === "--address" && index < process.argv.length - 1) { - host = process.argv[index + 1]; - } + if (e === "-a" || (e === "--address" && index < process.argv.length - 1)) { + host = process.argv[index + 1]; + } }); if (!dirname.endsWith("/")) dirname = dirname + "/"; const server = new net.Server((res) => { - // if There's no stream, Do nothing and act nothing. - if (!stream) play(); - let generateId = () => Math.random().toString(36).slice(2); - let id = generateId() + generateId() + generateId() + generateId() + generateId() + generateId(); - logs.push(`[${Date()}] New Sink Connected (${id})`); - sink.set(id, res); - res.on("close", () => { - logs.push(`[${Date()}] Sink (${id}) Disconnected.`); - sink.delete(id); - }); - res.on("error", err => { - logs.push(`[${Date()}] ${err}`); - logs.push(`[${Date()}] Sink (${id}) Disconnected.`); - sink.delete(id); - }); - }) - .listen(port, host||"0.0.0.0", () => { - console.log("---> Radio started at port:", port); - console.log(`---> Send request to tcp://${host||"0.0.0.0"}:` + port + " to Start radio"); - console.log("---> Or type command to manage radio"); - console.log("---> Or Press Enter to Play the radio stadion in Background."); - console.log("\nFor command list, Type `help`"); - rl.prompt() - }); + // if There's no stream, Do nothing and act nothing. + if (!stream) play(); + let generateId = () => Math.random().toString(36).slice(2); + let id = + generateId() + + generateId() + + generateId() + + generateId() + + generateId() + + generateId(); + logs.push(`[${Date()}] New Sink Connected (${id})`); + sink.set(id, res); + res.on("close", () => { + logs.push(`[${Date()}] Sink (${id}) Disconnected.`); + sink.delete(id); + }); + res.on("error", (err) => { + logs.push(`[${Date()}] ${err}`); + logs.push(`[${Date()}] Sink (${id}) Disconnected.`); + sink.delete(id); + }); +}).listen(port, host || "0.0.0.0", () => { + console.log("---> Radio started at port:", port); + console.log( + `---> Send request to tcp://${host || "0.0.0.0"}:` + + port + + " to Start radio" + ); + console.log("---> Or type command to manage radio"); + console.log("---> Or Press Enter to Play the radio stadion in Background."); + console.log("\nFor command list, Type `help`"); + rl.prompt(); +}); const Fs = require("fs"); const { extname } = require("path"); const _readDir = () => Fs.readdirSync(dirname, { withFileTypes: true }); -const _isAudio = item => (item.isFile && compatibleFormat.includes(extname(item.name))); +const _isAudio = (item) => + item.isFile && compatibleFormat.includes(extname(item.name)); let manager = {}; function convert(filename) { - return new ffmpeg({ - args: ["-i", filename, "-analyzeduration", "0", "-loglevel", "0", "-f", "mp3", "-ar", "48000", "-ac", "2", "-ab", "192k", "-map", "0:a", "-map_metadata", "-1"], - }); + return new ffmpeg({ + args: [ + "-i", + filename, + "-analyzeduration", + "0", + "-loglevel", + "0", + "-f", + "mp3", + "-ar", + "48000", + "-ac", + "2", + "-ab", + "192k", + "-map", + "0:a", + "-map_metadata", + "-1", + ], + }); } manager.readSong = () => _readDir().filter(_isAudio)[0].name; manager.readSongs = () => - _readDir() - .filter(_isAudio) - .map((songItem) => songItem.name); + _readDir() + .filter(_isAudio) + .map((songItem) => songItem.name); manager.discardFirstWord = (str) => str.substring(str.indexOf(" ") + 1); manager.getFirstWord = (str) => str.split(" ")[0]; @@ -79,200 +108,207 @@ manager.getFirstWord = (str) => str.split(" ")[0]; console.log("Total Songs in Directory:", manager.readSongs().length); async function play(n) { - let song = manager.readSongs(); - if (manager.readSongs().length === 0) { - console.log("There's no songs in this directory. Please put one and try again!"); - return rl.prompt() - } - if (stream) { - if (stream.playing) return; - } - let filename = song[new Number(n) - 1] || song[songnum++]; - if (n) songnum = n; + let song = manager.readSongs(); + if (manager.readSongs().length === 0) { + console.log( + "There's no songs in this directory. Please put one and try again!" + ); + return rl.prompt(); + } + if (stream) { + if (stream.playing) return; + } + let filename = song[new Number(n) - 1] || song[songnum++]; + if (n) songnum = n; - if (loop) { - if (!n) { - if (songnum != 1) { - songnum = songnum - 1; - filename = song[songnum - 1]; - } else { - songnum = manager.readSongs().length; - filename = song[songnum - 1]; - } - } + if (loop) { + if (!n) { + if (songnum != 1) { + songnum = songnum - 1; + filename = song[songnum - 1]; + } else { + songnum = manager.readSongs().length; + filename = song[songnum - 1]; + } } - if (random) { - if (!n) { - songnum = Math.floor(Math.random() * song.length); - filename = song[songnum - 1]; - } + } + if (random) { + if (!n) { + songnum = Math.floor(Math.random() * song.length); + filename = song[songnum - 1]; } + } - if (!filename || !songnum) { - filename = song[0]; - songnum = 1; - } - - stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); - stream.on("data", (chunk) => { - sink.forEach((s) => { - s.write(chunk); - }); - }); - stream.playing = true; - stream.on("end", () => { - if (!stream) return; - if (stream.stopped) return; - stream.playing = false; - play(); - }); + if (!filename || !songnum) { + filename = song[0]; + songnum = 1; + } + + stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); + stream.on("data", (chunk) => { + sink.forEach((s) => { + s.write(chunk); + }); + }); + stream.playing = true; + stream.on("end", () => { + if (!stream) return; + if (stream.stopped) return; + stream.playing = false; + play(); + }); - stream.on('error', err => { - console.error(`An error occured when playing ${filename}:`, err); - console.log("Skipping...."); - play(); - }); + stream.on("error", (err) => { + console.error(`An error occured when playing ${filename}:`, err); + console.log("Skipping...."); + play(); + }); - console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); - np = filename; - rl.prompt() + console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); + np = filename; + rl.prompt(); } -rl.on('line', str => { - if (str) { - let command = str.split(" ")[0]; - if (command === "help") { - console.log("skip -", "Skip & Play other song"); - console.log("np -", "Showing Current playing song name"); - console.log("q / ls -", "Showing song name in current folder"); - console.log("p -", "Skip & play provided song number"); - console.log("stop -", "Stop the player"); - console.log("logs -", "Show TCP Traffic Logs"); - console.log("clearlogs -", "Clear logs"); - console.log("sink -", "Show all Sink name"); - console.log("loop -", "Loop the current song"); - console.log("random -", "Enable Random song fetching"); - console.log("pause -", "Pause the radio"); - console.log("resume -", "Resume the radio"); - console.log("cd -", "Change directory"); - console.log("\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`"); - } else if (command === "skip") { - if (!stream) return console.log("Nothing Playing."); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - return play(); - } else if (command === "np") { - if (!np) return console.log("Nothing Playing."); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } else if (command === "q" || command === "ls") { - console.log("############### Song List ###############\n"); - let cl = 1; - manager.readSongs().forEach((e) => { - console.log(`[${cl}]`, e); - cl++; - }); - if (np) { - console.log(""); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } - } else if (command === "p" || command === "play") { - let songnumber = Number(str.split(" ")[1]); - if (!songnumber) { - console.log("Usage: p [Song number]"); - console.log("To get song number, Do `q`\n"); - return rl.prompt() - } - let sname = manager.readSongs()[songnumber - 1]; - if (!sname) { - console.log("Song not found\n"); - return rl.prompt() - } - if (!stream) return play(songnumber); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - play(songnumber); - return; - } else if (command === "stop") { - if (!stream) return console.log('Nothing playing.'); - stream.stopped = true; - stream.playing = false; - np = null; - stream.end(); - } else if (command === "logs") { - console.log(logs.join("\n")); - } else if (command === "clearlogs") { - logs = []; - } else if (command === "sink") { - a = 1; - let args = str.split(" ").slice(1).join(" "); - if (!args) { - console.log("--------------------- Sink Manager -"); - console.log("--- ID -----------------------------"); - sink.forEach((res, name) => { - console.log(`${a}. ${name}`); - a++; - }); - console.log("------------------------------------\nTotal Sink:", sink.size); - console.log("To remove sink ID, Execute `.sink remove `"); - } else { - if (args.startsWith("remove")) { - let id = args.split(" ").slice(1).join(" "); - if (!id) { - console.log("Usage: .sink remove "); - } else { - let res = sink.get(id); - if (!res) { - console.log("There's no Sink ID", res); - } else { - res.end(); - sink.delete(id); - console.log("--> Removed Sink", id); - console.log("Total Sink:", sink.size); - } - } - } - } - } else if (command === "loop") { - if (random) random = false; - if (!loop) { - loop = true; - console.log("Loop is now enabled."); - } else { - loop = false; - console.log("Loop is now disabled"); - } - } else if (command === "random") { - if (loop) loop = false; - if (!random) { - random = true; - console.log("Random mode is now enabled"); +rl.on("line", (str) => { + if (str) { + let command = str.split(" ")[0]; + if (command === "help") { + console.log("skip -", "Skip & Play other song"); + console.log("np -", "Showing Current playing song name"); + console.log("q / ls -", "Showing song name in current folder"); + console.log("p -", "Skip & play provided song number"); + console.log("stop -", "Stop the player"); + console.log("logs -", "Show TCP Traffic Logs"); + console.log("clearlogs -", "Clear logs"); + console.log("sink -", "Show all Sink name"); + console.log("loop -", "Loop the current song"); + console.log("random -", "Enable Random song fetching"); + console.log("pause -", "Pause the radio"); + console.log("resume -", "Resume the radio"); + console.log("cd -", "Change directory"); + console.log( + "\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`" + ); + } else if (command === "skip") { + if (!stream) return console.log("Nothing Playing."); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + return play(); + } else if (command === "np") { + if (!np) return console.log("Nothing Playing."); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } else if (command === "q" || command === "ls") { + console.log("############### Song List ###############\n"); + let cl = 1; + manager.readSongs().forEach((e) => { + console.log(`[${cl}]`, e); + cl++; + }); + if (np) { + console.log(""); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } + } else if (command === "p" || command === "play") { + let songnumber = Number(str.split(" ")[1]); + if (!songnumber) { + console.log("Usage: p [Song number]"); + console.log("To get song number, Do `q`\n"); + return rl.prompt(); + } + let sname = manager.readSongs()[songnumber - 1]; + if (!sname) { + console.log("Song not found\n"); + return rl.prompt(); + } + if (!stream) return play(songnumber); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + play(songnumber); + return; + } else if (command === "stop") { + if (!stream) return console.log("Nothing playing."); + stream.stopped = true; + stream.playing = false; + np = null; + stream.end(); + } else if (command === "logs") { + console.log(logs.join("\n")); + } else if (command === "clearlogs") { + logs = []; + } else if (command === "sink") { + a = 1; + let args = str.split(" ").slice(1).join(" "); + if (!args) { + console.log("--------------------- Sink Manager -"); + console.log("--- ID -----------------------------"); + sink.forEach((res, name) => { + console.log(`${a}. ${name}`); + a++; + }); + console.log( + "------------------------------------\nTotal Sink:", + sink.size + ); + console.log("To remove sink ID, Execute `.sink remove `"); + } else { + if (args.startsWith("remove")) { + let id = args.split(" ").slice(1).join(" "); + if (!id) { + console.log("Usage: .sink remove "); + } else { + let res = sink.get(id); + if (!res) { + console.log("There's no Sink ID", res); } else { - random = false; - console.log("Random mode is now Disabled"); + res.end(); + sink.delete(id); + console.log("--> Removed Sink", id); + console.log("Total Sink:", sink.size); } - } else if (command === "pause") { - if (!stream) return console.log('Nothing playing.'); - stream.pause(); - } else if (command === "resume") { - if (!stream) return console.log('Nothing playing.'); - stream.resume(); - } else if (command === "cd") { - let args = str.split(" ").slice(1).join(" "); - try { - process.chdir(args || process.env.HOME); - dirname = process.cwd() + "/"; - } catch (error) { - console.error(error); - } + } } - } else { - if (!stream) play(); + } + } else if (command === "loop") { + if (random) random = false; + if (!loop) { + loop = true; + console.log("Loop is now enabled."); + } else { + loop = false; + console.log("Loop is now disabled"); + } + } else if (command === "random") { + if (loop) loop = false; + if (!random) { + random = true; + console.log("Random mode is now enabled"); + } else { + random = false; + console.log("Random mode is now Disabled"); + } + } else if (command === "pause") { + if (!stream) return console.log("Nothing playing."); + stream.pause(); + } else if (command === "resume") { + if (!stream) return console.log("Nothing playing."); + stream.resume(); + } else if (command === "cd") { + let args = str.split(" ").slice(1).join(" "); + try { + process.chdir(args || process.env.HOME); + dirname = process.cwd() + "/"; + } catch (error) { + console.error(error); + } } - rl.prompt(); + } else { + if (!stream) play(); + } + rl.prompt(); }); rl.setPrompt("Command > "); diff --git a/cli/udp.js b/cli/udp.js index 50a9194..f02a0fc 100755 --- a/cli/udp.js +++ b/cli/udp.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const Throttle = require("throttle"); -const ffmpeg = require('prism-media').FFmpeg; +const ffmpeg = require("prism-media").FFmpeg; const compatibleFormat = require("./compatibleFormat.json"); const dgram = require("dgram"); const readline = require("readline"); @@ -21,29 +21,39 @@ let random = false; process.title = "OpenRadio"; process.argv.forEach((e, index) => { - if (e === "-a" || e === "--address" && index < process.argv.length - 1) { - host = process.argv[index + 1]; - } + if (e === "-a" || (e === "--address" && index < process.argv.length - 1)) { + host = process.argv[index + 1]; + } }); if (!dirname.endsWith("/")) dirname = dirname + "/"; const server = new dgram.createSocket("udp4"); -server.on('message', (res, remote) => { - // if There's no stream, Do nothing and act nothing. - if (!stream) play(); - let generateId = () => Math.random().toString(36).slice(2); - let id = generateId() + generateId() + generateId() + generateId() + generateId() + generateId(); - logs.push(`[${Date()}] New Sink Connected (${id})`); - sink.set(id, remote); +server.on("message", (res, remote) => { + // if There's no stream, Do nothing and act nothing. + if (!stream) play(); + let generateId = () => Math.random().toString(36).slice(2); + let id = + generateId() + + generateId() + + generateId() + + generateId() + + generateId() + + generateId(); + logs.push(`[${Date()}] New Sink Connected (${id})`); + sink.set(id, remote); }); -server.bind(port, host||"0.0.0.0", () => { - console.log("---> Radio binded at port:", port); - console.log(`---> Send ANY MESSAGE to udp://${host||"0.0.0.0"}:` + port + " to Start radio"); - console.log("---> Or type command to manage radio"); - console.log("---> Or Press Enter to Play the radio stadion in Background."); - console.log("\nFor command list, Type `help`"); - rl.prompt(); +server.bind(port, host || "0.0.0.0", () => { + console.log("---> Radio binded at port:", port); + console.log( + `---> Send ANY MESSAGE to udp://${host || "0.0.0.0"}:` + + port + + " to Start radio" + ); + console.log("---> Or type command to manage radio"); + console.log("---> Or Press Enter to Play the radio stadion in Background."); + console.log("\nFor command list, Type `help`"); + rl.prompt(); }); const Fs = require("fs"); @@ -51,21 +61,41 @@ const { extname } = require("path"); const _readDir = () => Fs.readdirSync(dirname, { withFileTypes: true }); -const _isAudio = item => (item.isFile && compatibleFormat.includes(extname(item.name))); +const _isAudio = (item) => + item.isFile && compatibleFormat.includes(extname(item.name)); let manager = {}; function convert(filename) { - return new ffmpeg({ - args: ["-i", filename, "-analyzeduration", "0", "-loglevel", "0", "-f", "mp3", "-ar", "48000", "-ac", "2", "-ab", "192k", "-map", "0:a", "-map_metadata", "-1"], - }); + return new ffmpeg({ + args: [ + "-i", + filename, + "-analyzeduration", + "0", + "-loglevel", + "0", + "-f", + "mp3", + "-ar", + "48000", + "-ac", + "2", + "-ab", + "192k", + "-map", + "0:a", + "-map_metadata", + "-1", + ], + }); } manager.readSong = () => _readDir().filter(_isAudio)[0].name; manager.readSongs = () => - _readDir() - .filter(_isAudio) - .map((songItem) => songItem.name); + _readDir() + .filter(_isAudio) + .map((songItem) => songItem.name); manager.discardFirstWord = (str) => str.substring(str.indexOf(" ") + 1); manager.getFirstWord = (str) => str.split(" ")[0]; @@ -73,223 +103,231 @@ manager.getFirstWord = (str) => str.split(" ")[0]; console.log("Total Songs in Directory:", manager.readSongs().length); async function play(n) { - let song = manager.readSongs(); - if (manager.readSongs().length === 0) { - console.log("There's no songs in this directory. Please put one and try again!"); - return rl.prompt(); - } - if (stream) { - if (stream.playing) return; - } - let filename = song[new Number(n) - 1] || song[songnum++]; - if (n) songnum = n; + let song = manager.readSongs(); + if (manager.readSongs().length === 0) { + console.log( + "There's no songs in this directory. Please put one and try again!" + ); + return rl.prompt(); + } + if (stream) { + if (stream.playing) return; + } + let filename = song[new Number(n) - 1] || song[songnum++]; + if (n) songnum = n; - if (loop) { - if (!n) { - if (songnum != 1) { - songnum = songnum - 1; - filename = song[songnum - 1]; - } else { - songnum = manager.readSongs().length; - filename = song[songnum - 1]; - } - } + if (loop) { + if (!n) { + if (songnum != 1) { + songnum = songnum - 1; + filename = song[songnum - 1]; + } else { + songnum = manager.readSongs().length; + filename = song[songnum - 1]; + } } - if (random) { - if (!n) { - songnum = Math.floor(Math.random() * song.length); - filename = song[songnum - 1]; - } + } + if (random) { + if (!n) { + songnum = Math.floor(Math.random() * song.length); + filename = song[songnum - 1]; } + } - if (!filename || !songnum) { - filename = song[0]; - songnum = 1; - } - - stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); - stream.on("data", (chunk) => { - sink.forEach((s, id) => { - server.send(chunk, 0, chunk.length, s.port, s.address, (err) => { - if (err) { - sink.delete(id); - } - }); - }); - }); - stream.playing = true; - stream.on("end", () => { - if (!stream) return; - if (stream.stopped) return; - stream.playing = false; - play(); - }); + if (!filename || !songnum) { + filename = song[0]; + songnum = 1; + } - stream.on('error', err => { - console.error(`An error occured when playing ${filename}:`, err); - console.log("Skipping...."); - play(); - }); + stream = convert(`${dirname}/${filename}`).pipe(Throttle(24000)); + stream.on("data", (chunk) => { + sink.forEach((s, id) => { + server.send(chunk, 0, chunk.length, s.port, s.address, (err) => { + if (err) { + sink.delete(id); + } + }); + }); + }); + stream.playing = true; + stream.on("end", () => { + if (!stream) return; + if (stream.stopped) return; + stream.playing = false; + play(); + }); + + stream.on("error", (err) => { + console.error(`An error occured when playing ${filename}:`, err); + console.log("Skipping...."); + play(); + }); - console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); - np = filename; - rl.prompt(); + console.log("\n--> Now Playing:", `[${songnum}] ${filename}`); + np = filename; + rl.prompt(); } -rl.on('line', str => { - if (str) { - let command = str.split(" ")[0]; - if (command === "help") { - console.log("skip -", "Skip & Play other song"); - console.log("np -", "Showing Current playing song name"); - console.log("q / ls -", "Showing song name in current folder"); - console.log("p -", "Skip & play provided song number"); - console.log("stop -", "Stop the player"); - console.log("logs -", "Show UDP Traffic Logs"); - console.log("clearlogs -", "Clear logs"); - console.log("sink -", "Show all Sink name"); - console.log("loop -", "Loop the current song"); - console.log("random -", "Enable Random song fetching"); - console.log("pause -", "Pause the radio"); - console.log("resume -", "Resume the radio"); - console.log("connect -", "Connect & Send Packet"); - console.log("cd -", "Change directory"); - console.log("\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`"); - } else if (command === "skip") { - if (!stream) return console.log("Nothing Playing."); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - return play(); - } else if (command === "np") { - if (!np) return console.log("Nothing Playing."); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } else if (command === "q" || command === "ls") { - console.log("############### Song List ###############\n"); - let cl = 1; - manager.readSongs().forEach((e) => { - console.log(`[${cl}]`, e); - cl++; - }); - if (np) { - console.log(""); - console.log("############### Now Playing ###############\n"); - console.log(`[${songnum}]`, np); - } - } else if (command === "p" || command === "play") { - let songnumber = Number(str.split(" ").slice(1)[0]); - if (!songnumber) { - console.log("Usage: p [Song number]"); - console.log("To get song number, Do `q`\n"); - return rl.prompt(); - } - let sname = manager.readSongs()[songnumber - 1]; - if (!sname) { - console.log("Song not found\n"); - return rl.prompt(); - } - if (!stream) return play(songnumber); - stream.playing = false; - stream.stopped = true; - stream.destroy(); - play(songnumber); - return; - } else if (command === "stop") { - if (!stream) return console.log('Nothing playing.'); - stream.stopped = true; - stream.playing = false; - np = null; - stream.end(); - } else if (command === "logs") { - console.log(logs.join("\n")); - } else if (command === "clearlogs") { - logs = []; - } else if (command === "sink") { - a = 1; - let args = str.split(" ").slice(1).join(" "); - if (!args) { - console.log("--------------------- Sink Manager -"); - console.log("--- ID -----------------------------"); - sink.forEach((res, name) => { - console.log(`${a}. ${name}`); - a++; - }); - console.log("------------------------------------\nTotal Sink:", sink.size); - console.log("To remove sink ID, Execute `.sink remove `"); - } else { - if (args.startsWith("remove")) { - let id = args.split(" ").slice(1).join(" "); - if (!id) { - console.log("Usage: .sink remove "); - } else { - let res = sink.get(id); - if (!res) { - console.log("There's no Sink ID", res); - } else { - res.end(); - sink.delete(id); - console.log("--> Removed Sink", id); - console.log("Total Sink:", sink.size); - } - } - } - } - } else if (command === "loop") { - if (random) random = false; - if (!loop) { - loop = true; - console.log("Loop is now enabled."); - } else { - loop = false; - console.log("Loop is now disabled"); - } - } else if (command === "random") { - if (loop) loop = false; - if (!random) { - random = true; - console.log("Random mode is now enabled"); +rl.on("line", (str) => { + if (str) { + let command = str.split(" ")[0]; + if (command === "help") { + console.log("skip -", "Skip & Play other song"); + console.log("np -", "Showing Current playing song name"); + console.log("q / ls -", "Showing song name in current folder"); + console.log("p -", "Skip & play provided song number"); + console.log("stop -", "Stop the player"); + console.log("logs -", "Show UDP Traffic Logs"); + console.log("clearlogs -", "Clear logs"); + console.log("sink -", "Show all Sink name"); + console.log("loop -", "Loop the current song"); + console.log("random -", "Enable Random song fetching"); + console.log("pause -", "Pause the radio"); + console.log("resume -", "Resume the radio"); + console.log("connect -", "Connect & Send Packet"); + console.log("cd -", "Change directory"); + console.log( + "\nTo listen to different Address, Do `openradio 3000 . -a 127.0.0.1`" + ); + } else if (command === "skip") { + if (!stream) return console.log("Nothing Playing."); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + return play(); + } else if (command === "np") { + if (!np) return console.log("Nothing Playing."); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } else if (command === "q" || command === "ls") { + console.log("############### Song List ###############\n"); + let cl = 1; + manager.readSongs().forEach((e) => { + console.log(`[${cl}]`, e); + cl++; + }); + if (np) { + console.log(""); + console.log("############### Now Playing ###############\n"); + console.log(`[${songnum}]`, np); + } + } else if (command === "p" || command === "play") { + let songnumber = Number(str.split(" ").slice(1)[0]); + if (!songnumber) { + console.log("Usage: p [Song number]"); + console.log("To get song number, Do `q`\n"); + return rl.prompt(); + } + let sname = manager.readSongs()[songnumber - 1]; + if (!sname) { + console.log("Song not found\n"); + return rl.prompt(); + } + if (!stream) return play(songnumber); + stream.playing = false; + stream.stopped = true; + stream.destroy(); + play(songnumber); + return; + } else if (command === "stop") { + if (!stream) return console.log("Nothing playing."); + stream.stopped = true; + stream.playing = false; + np = null; + stream.end(); + } else if (command === "logs") { + console.log(logs.join("\n")); + } else if (command === "clearlogs") { + logs = []; + } else if (command === "sink") { + a = 1; + let args = str.split(" ").slice(1).join(" "); + if (!args) { + console.log("--------------------- Sink Manager -"); + console.log("--- ID -----------------------------"); + sink.forEach((res, name) => { + console.log(`${a}. ${name}`); + a++; + }); + console.log( + "------------------------------------\nTotal Sink:", + sink.size + ); + console.log("To remove sink ID, Execute `.sink remove `"); + } else { + if (args.startsWith("remove")) { + let id = args.split(" ").slice(1).join(" "); + if (!id) { + console.log("Usage: .sink remove "); + } else { + let res = sink.get(id); + if (!res) { + console.log("There's no Sink ID", res); } else { - random = false; - console.log("Random mode is now Disabled"); + res.end(); + sink.delete(id); + console.log("--> Removed Sink", id); + console.log("Total Sink:", sink.size); } - } else if (command === "pause") { - if (!stream) return console.log('Nothing Playing.'); - stream.pause(); - } else if (command === "resume") { - if (!stream) return console.log('Nothing Playing.'); - stream.resume(); - } else if (command === "connect") { - let args = str.split(" ").slice(1).join(" "); - if (!args) { - console.log("Usage: connect [host:port]"); - console.log("Send a buffer to another UDP client that binded their port. Mostly used for sending packet to ffmpeg/mpv player."); - } else { - let uri = args.split(":"); - if (uri[0].startsWith("udp://")) { - uri[0] = uri.slice(6); - } - if (!uri[0] || !uri[1]) return console.error("Invalid address"); - sink.set(`${uri[0]}:${uri[1]}`, { - address: uri[0], - port: uri[1] - }); - console.log("Now sending packet to:", `${uri[0]}:${uri[1]}`); - } - } else if (command === "cd") { - let args = str.split(" ").slice(1).join(" "); - try { - process.chdir(args || process.env.HOME); - dirname = process.cwd() + "/"; - } catch (error) { - console.error(error); - } - } - } else { - if (!stream) play(); + } + } + } + } else if (command === "loop") { + if (random) random = false; + if (!loop) { + loop = true; + console.log("Loop is now enabled."); + } else { + loop = false; + console.log("Loop is now disabled"); + } + } else if (command === "random") { + if (loop) loop = false; + if (!random) { + random = true; + console.log("Random mode is now enabled"); + } else { + random = false; + console.log("Random mode is now Disabled"); + } + } else if (command === "pause") { + if (!stream) return console.log("Nothing Playing."); + stream.pause(); + } else if (command === "resume") { + if (!stream) return console.log("Nothing Playing."); + stream.resume(); + } else if (command === "connect") { + let args = str.split(" ").slice(1).join(" "); + if (!args) { + console.log("Usage: connect [host:port]"); + console.log( + "Send a buffer to another UDP client that binded their port. Mostly used for sending packet to ffmpeg/mpv player." + ); + } else { + let uri = args.split(":"); + if (uri[0].startsWith("udp://")) { + uri[0] = uri.slice(6); + } + if (!uri[0] || !uri[1]) return console.error("Invalid address"); + sink.set(`${uri[0]}:${uri[1]}`, { + address: uri[0], + port: uri[1], + }); + console.log("Now sending packet to:", `${uri[0]}:${uri[1]}`); + } + } else if (command === "cd") { + let args = str.split(" ").slice(1).join(" "); + try { + process.chdir(args || process.env.HOME); + dirname = process.cwd() + "/"; + } catch (error) { + console.error(error); + } } - rl.prompt(); + } else { + if (!stream) play(); + } + rl.prompt(); }); rl.setPrompt("Command > "); - diff --git a/core/index.js b/core/index.js index ee91aa7..b1a4e14 100644 --- a/core/index.js +++ b/core/index.js @@ -5,66 +5,137 @@ const Throttle = require("throttle"); const events = require("events"); function convert(opt = {}) { - return new ffmpeg({ - args: ["-analyzeduration", "0", "-loglevel", "0", "-f", opt.format || "mp3", "-ar", opt.rate || "48000", "-ac", opt.channels || "2", "-ab", `${opt.bitrate || "96"}k`, "-map", "0:a", "-map_metadata", "-1"] - }); + return new ffmpeg({ + args: [ + "-analyzeduration", + "0", + "-loglevel", + "0", + "-f", + opt.format || "mp3", + "-ar", + opt.rate || "48000", + "-ac", + opt.channels || "2", + "-ab", + `${opt.bitrate || "96"}k`, + "-map", + "0:a", + "-map_metadata", + "-1", + ], + }); } function pcmconv(opt = {}, pcmopt) { - return new ffmpeg({ - args: ["-analyzeduration", "0", "-loglevel", "0", "-f", "s16le", "-ac", pcmopt.channels || 2, "-ar", pcmopt.rate || "44100", "-i", "-", "-f", opt.format || "mp3", "-ar", opt.rate || "48000", "-ac", opt.channels || "2", "-ab", `${opt.bitrate || "96"}k`, "-map", "0:a", "-map_metadata", "-1"] - }); + return new ffmpeg({ + args: [ + "-analyzeduration", + "0", + "-loglevel", + "0", + "-f", + "s16le", + "-ac", + pcmopt.channels || 2, + "-ar", + pcmopt.rate || "44100", + "-i", + "-", + "-f", + opt.format || "mp3", + "-ar", + opt.rate || "48000", + "-ac", + opt.channels || "2", + "-ab", + `${opt.bitrate || "96"}k`, + "-map", + "0:a", + "-map_metadata", + "-1", + ], + }); } function OpenRadio_Core(opt) { - let Core = new PassThrough(); - let converted = null; + let Core = new PassThrough(); + let converted = null; + + Core.playing = false; + Core.finish = false; + Core.stream = null; - Core.playing = false; - Core.finish = false; - Core.stream = null; - - // Player - Core.play = function ReadStream(readable) { - return new Promise((res, rej) => { - if (Core.stream && "destroyed" in Core.stream && !Core.stream.destroyed) Core.stream.destroy(); - Core.stream = readable.pipe(convert(opt)).on('error', e => Core.emit('error', e)).pipe(Throttle(((() => { if (opt && opt.bitrate) return opt.bitrate * 1000 })() || 96000) / 8)).on("data", (chunk) => { - Core.write(chunk); - }).on("end", (e) => { - Core.emit("finish", e); - Core.finish = true; - Core.playing = false; - return res(e); - }).on("error", (err) => { - if (!Core.emit("error", err)) return rej(err); - }); - readable.on("error", (err) => Core.emit("error", err)); - Core.playing = true; - Core.finish = false; + // Player + Core.play = function ReadStream(readable) { + return new Promise((res, rej) => { + if (Core.stream && "destroyed" in Core.stream && !Core.stream.destroyed) + Core.stream.destroy(); + Core.stream = readable + .pipe(convert(opt)) + .on("error", (e) => Core.emit("error", e)) + .pipe( + Throttle( + ((() => { + if (opt && opt.bitrate) return opt.bitrate * 1000; + })() || 96000) / 8 + ) + ) + .on("data", (chunk) => { + Core.write(chunk); + }) + .on("end", (e) => { + Core.emit("finish", e); + Core.finish = true; + Core.playing = false; + return res(e); + }) + .on("error", (err) => { + if (!Core.emit("error", err)) return rej(err); }); - }; + readable.on("error", (err) => Core.emit("error", err)); + Core.playing = true; + Core.finish = false; + }); + }; - // PCM Player - Core.playPCM = function PCMPlayer(readable, options = { rate: 44100, channels: 2 }) { - return new Promise((res, rej) => { - if (Core.stream && "destroyed" in Core.stream && !Core.stream.destroyed) Core.stream.destroy(); - Core.stream = readable.pipe(pcmconv(opt, options)).on('error', e => Core.emit('error', e)).pipe(Throttle(((() => { if (opt && opt.bitrate) return opt.bitrate * 1000 })() || 96000) / 8)).on("data", (chunk) => { - Core.write(chunk); - }).on("end", (e) => { - Core.emit('finish', e); - Core.finish = true; - Core.playing = false; - return res(e); - }).on("error", (err) => { - if (!Core.emit("error", err)) return rej(err); - }); - readable.on("error", (err) => Core.emit("error", err)); - Core.playing = true; - Core.finish = true; - }); - } + // PCM Player + Core.playPCM = function PCMPlayer( + readable, + options = { rate: 44100, channels: 2 } + ) { + return new Promise((res, rej) => { + if (Core.stream && "destroyed" in Core.stream && !Core.stream.destroyed) + Core.stream.destroy(); + Core.stream = readable + .pipe(pcmconv(opt, options)) + .on("error", (e) => Core.emit("error", e)) + .pipe( + Throttle( + ((() => { + if (opt && opt.bitrate) return opt.bitrate * 1000; + })() || 96000) / 8 + ) + ) + .on("data", (chunk) => { + Core.write(chunk); + }) + .on("end", (e) => { + Core.emit("finish", e); + Core.finish = true; + Core.playing = false; + return res(e); + }) + .on("error", (err) => { + if (!Core.emit("error", err)) return rej(err); + }); + readable.on("error", (err) => Core.emit("error", err)); + Core.playing = true; + Core.finish = true; + }); + }; - return Core; + return Core; } module.exports = OpenRadio_Core; diff --git a/package.json b/package.json index 073ec15..b0ed646 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openradio", - "version": "1.1.37", + "version": "1.1.40", "description": "Let you create your own livestream radio.", "main": "./core", "scripts": {