From 382d5afc7e2ac24f67b7c891328b8b9bb7e91058 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 13 Jul 2021 23:14:20 -0300 Subject: timestamp files!!! --- players.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'players.js') diff --git a/players.js b/players.js index e22e505..b41ce0c 100644 --- a/players.js +++ b/players.js @@ -37,7 +37,7 @@ class Player extends EventEmitter { return this._process } - playFile(file) {} + playFile(file, startTime) {} seekAhead(secs) {} seekBack(secs) {} seekTo(timeInSecs) {} @@ -87,7 +87,7 @@ class Player extends EventEmitter { } module.exports.MPVPlayer = class extends Player { - getMPVOptions(file) { + getMPVOptions(file, startTime) { const opts = ['--no-video', file] if (this.isLooping) { opts.unshift('--loop') @@ -95,15 +95,18 @@ module.exports.MPVPlayer = class extends Player { if (this.isPaused) { opts.unshift('--pause') } + if (startTime) { + opts.unshift('--start=' + startTime) + } opts.unshift('--volume=' + this.volume * this.volumeMultiplier) return opts } - playFile(file) { + playFile(file, startTime) { // 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.process = spawn('mpv', this.getMPVOptions(file).concat(this.processOptions)) + this.process = spawn('mpv', this.getMPVOptions(file, startTime).concat(this.processOptions)) let lastPercent = 0 @@ -146,11 +149,11 @@ module.exports.MPVPlayer = class extends Player { } module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { - getMPVOptions(file) { - return ['--input-ipc-server=' + this.socat.path, ...super.getMPVOptions(file)] + getMPVOptions(...args) { + return ['--input-ipc-server=' + this.socat.path, ...super.getMPVOptions(...args)] } - playFile(file) { + playFile(file, startTime) { this.removeSocket(this.socketPath) do { @@ -160,7 +163,7 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { this.socat = new Socat(this.socketPath) - const mpv = super.playFile(file) + const mpv = super.playFile(file, startTime) mpv.then(() => this.removeSocket(this.socketPath)) @@ -252,13 +255,16 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { } module.exports.SoXPlayer = class extends Player { - playFile(file) { + playFile(file, startTime) { // 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', [file].concat(this.processOptions)) + this.process = spawn('play', [file].concat( + this.processOptions, + startTime ? ['trim', startTime] : [] + )) this.process.stdout.on('data', data => { process.stdout.write(data.toString()) -- cgit 1.3.0-6-gf8a5 From 7af31d6ccb2d1b0c47c0bbb60a7e51c64bb01bf1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 17 Jul 2021 20:09:09 -0300 Subject: past 3 second threshold, (P) to seek to start --- players.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'players.js') diff --git a/players.js b/players.js index b41ce0c..c707494 100644 --- a/players.js +++ b/players.js @@ -41,6 +41,7 @@ class Player extends EventEmitter { seekAhead(secs) {} seekBack(secs) {} seekTo(timeInSecs) {} + seekToStart() {} volUp(amount) {} volDown(amount) {} setVolume(value) {} @@ -197,6 +198,10 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { this.sendCommand('seek', timeInSecs, 'absolute') } + seekToStart() { + this.seekTo(0) + } + volUp(amount) { this.setVolume(this.volume + amount) } @@ -261,6 +266,8 @@ module.exports.SoXPlayer = class extends Player { // You don't get keyboard controls such as seeking or volume adjusting // with SoX, though. + this._file = file + this.process = spawn('play', [file].concat( this.processOptions, startTime ? ['trim', startTime] : [] @@ -313,8 +320,39 @@ module.exports.SoXPlayer = class extends Player { return new Promise(resolve => { this.process.on('close', () => resolve()) + }).then(() => { + if (this._restartPromise) { + const p = this._restartPromise + this._restartPromise = null + return p + } }) } + + async seekToStart() { + // SoX doesn't support a command interface to interact while playback is + // ongoing. However, we can simulate seeking to the start by restarting + // playback altogether. We just need to be careful not to resolve the + // original playback promise before the new one is complete! + + if (!this._file) { + return + } + + let resolve = null + let reject = null + + // The original call of playFile() will yield control to this promise, which + // we bind to the resolve/reject of a new call to playFile(). + this._restartPromise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + + await this.kill() + + this.playFile(this._file).then(resolve, reject) + } } module.exports.getPlayer = async function(name = null, options = []) { -- cgit 1.3.0-6-gf8a5 From 864b45520acae62962a10f335eab3950ed84c6fc Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 6 Aug 2021 18:27:44 -0300 Subject: get fractional playback pos & duration from mpv This fixes a side-effect of timestamp files with fractional timestamps: mtui always used to assume it was at .000 of the current second, so it would briefly highlight the previous timestamp before completely passing the second the timestamp starts within. --- players.js | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) (limited to 'players.js') diff --git a/players.js b/players.js index c707494..77f1246 100644 --- a/players.js +++ b/players.js @@ -1,7 +1,13 @@ // stolen from http-music +const { + commandExists, + killProcess, + getTimeStrings, + getTimeStringsFromSec +} = require('./general-util') + const { spawn } = require('child_process') -const { commandExists, killProcess, getTimeStrings } = require('./general-util') const EventEmitter = require('events') const Socat = require('./socat') const fs = require('fs') @@ -88,25 +94,42 @@ class Player extends EventEmitter { } module.exports.MPVPlayer = class extends Player { + // 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. + getMPVOptions(file, startTime) { - const opts = ['--no-video', file] + const opts = [ + `--term-status-msg='${this.getMPVStatusMessage()}'`, + '--no-video', + file + ] + if (this.isLooping) { opts.unshift('--loop') } + if (this.isPaused) { opts.unshift('--pause') } + if (startTime) { opts.unshift('--start=' + startTime) } + opts.unshift('--volume=' + this.volume * this.volumeMultiplier) + return opts } - playFile(file, startTime) { - // 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. + getMPVStatusMessage() { + // Note: This function shouldn't include any single-quotes! It probably + // (NOTE: PROBABLY) wouldn't cause any security issues, but it will break + // --term-status-msg parsing and might keep mpv from starting at all. + return '${=time-pos} ${=duration} ${=percent-pos}' + } + + playFile(file, startTime) { this.process = spawn('mpv', this.getMPVOptions(file, startTime).concat(this.processOptions)) let lastPercent = 0 @@ -117,14 +140,14 @@ module.exports.MPVPlayer = class extends Player { } const match = data.toString().match( - /(..):(..):(..) \/ (..):(..):(..) \(([0-9]+)%\)/ + /([0-9.]+) ([0-9.]+) ([0-9.]+)/ ) if (match) { const [ - curHour, curMin, curSec, // ##:##:## - lenHour, lenMin, lenSec, // ##:##:## - percent // ###% + curSecTotal, + lenSecTotal, + percent ] = match.slice(1) if (parseInt(percent) < lastPercent) { @@ -137,7 +160,7 @@ module.exports.MPVPlayer = class extends Player { lastPercent = parseInt(percent) - this.printStatusLine(getTimeStrings({curHour, curMin, curSec, lenHour, lenMin, lenSec})) + this.printStatusLine(getTimeStringsFromSec(curSecTotal, lenSecTotal)) } this.updateVolume(); -- cgit 1.3.0-6-gf8a5 From 3fdb4b7961f55a6b0fa24a3f271c3c8090497856 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 10 Oct 2021 10:41:29 -0300 Subject: fix setPause not working for MPV player This fixes the "Paused" option in the menubar! --- players.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'players.js') diff --git a/players.js b/players.js index 77f1246..1d64061 100644 --- a/players.js +++ b/players.js @@ -255,8 +255,15 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { } setPause(val) { + const wasPaused = this.isPaused this.isPaused = !!val - this.sendCommand('set', 'pause', this.isPaused) + + if (this.isPaused !== wasPaused) { + this.sendCommand('cycle', 'pause') + } + + // For some reason "set pause" doesn't seem to be working anymore: + // this.sendCommand('set', 'pause', this.isPaused) } setLoop(val) { -- cgit 1.3.0-6-gf8a5 From 43f1a1dd1b44065663a797603012394c52a9baea Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 13 May 2023 13:31:58 -0300 Subject: use ESM module syntax & update tui-lib Exciting update! This doesn't make any substantial changes exactly but does update the most quickly-archaic parts of older Node code. --- players.js | 56 +++++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) (limited to 'players.js') diff --git a/players.js b/players.js index 1d64061..959bf27 100644 --- a/players.js +++ b/players.js @@ -1,21 +1,22 @@ // stolen from http-music -const { +import { commandExists, killProcess, getTimeStrings, - getTimeStringsFromSec -} = require('./general-util') + getTimeStringsFromSec, +} from './general-util.js' -const { spawn } = require('child_process') -const EventEmitter = require('events') -const Socat = require('./socat') -const fs = require('fs') -const util = require('util') +import {spawn} from 'node:child_process' +import {statSync} from 'node:fs' +import {unlink} from 'node:fs/promises' +import EventEmitter from 'node:events' +import path from 'node:path' +import url from 'node:url' -const unlink = util.promisify(fs.unlink) +import Socat from './socat.js' -class Player extends EventEmitter { +export class Player extends EventEmitter { constructor(processOptions = []) { super() @@ -43,14 +44,14 @@ class Player extends EventEmitter { return this._process } - playFile(file, startTime) {} - seekAhead(secs) {} - seekBack(secs) {} - seekTo(timeInSecs) {} + playFile(_file, _startTime) {} + seekAhead(_secs) {} + seekBack(_secs) {} + seekTo(_timeInSecs) {} seekToStart() {} - volUp(amount) {} - volDown(amount) {} - setVolume(value) {} + volUp(_amount) {} + volDown(_amount) {} + setVolume(_value) {} updateVolume() {} togglePause() {} toggleLoop() {} @@ -93,7 +94,7 @@ class Player extends EventEmitter { } } -module.exports.MPVPlayer = class extends Player { +export class MPVPlayer extends Player { // 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. @@ -172,7 +173,7 @@ module.exports.MPVPlayer = class extends Player { } } -module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { +export class ControllableMPVPlayer extends MPVPlayer { getMPVOptions(...args) { return ['--input-ipc-server=' + this.socat.path, ...super.getMPVOptions(...args)] } @@ -181,8 +182,7 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { this.removeSocket(this.socketPath) do { - // this.socketPathpath = '/tmp/mtui-socket-' + Math.floor(Math.random() * 10000) - this.socketPath = __dirname + '/mtui-socket-' + Math.floor(Math.random() * 10000) + this.socketPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), 'mtui-socket-' + Math.floor(Math.random() * 10000)) } while (this.existsSync(this.socketPath)) this.socat = new Socat(this.socketPath) @@ -196,7 +196,7 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { existsSync(path) { try { - fs.statSync(path) + statSync(path) return true } catch (error) { return false @@ -289,7 +289,7 @@ module.exports.ControllableMPVPlayer = class extends module.exports.MPVPlayer { } } -module.exports.SoXPlayer = class extends Player { +export class SoXPlayer extends Player { playFile(file, startTime) { // 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). @@ -315,14 +315,12 @@ module.exports.SoXPlayer = class extends Player { return } - const timeRegex = '([0-9]*):([0-9]*):([0-9]*)\.([0-9]*)' + const timeRegex = String.raw`([0-9]*):([0-9]*):([0-9]*)\.([0-9]*)` const match = data.toString().trim().match(new RegExp( `^In:([0-9.]+%)\\s*${timeRegex}\\s*\\[${timeRegex}\\]` )) if (match) { - const percentStr = match[1] - // SoX takes a loooooot of math in order to actually figure out the // duration, since it outputs the current time and the remaining time // (but not the duration). @@ -385,15 +383,15 @@ module.exports.SoXPlayer = class extends Player { } } -module.exports.getPlayer = async function(name = null, options = []) { +export async function getPlayer(name = null, options = []) { if (await commandExists('mpv') && (name === null || name === 'mpv')) { - return new module.exports.ControllableMPVPlayer(options) + return new ControllableMPVPlayer(options) } else if (name === 'mpv') { return null } if (await commandExists('play') && (name === null || name === 'sox')) { - return new module.exports.SoXPlayer(options) + return new SoXPlayer(options) } else if (name === 'sox') { return null } -- cgit 1.3.0-6-gf8a5