node-mpv-2
Version:
A Node module for MPV
197 lines (168 loc) • 6.31 kB
JavaScript
// Child Process to module to start mpv player
const spawn = require('child_process').spawn;
const exec = require('child_process').execSync;
const net = require('net');
const path = require('path');
// EventEmitter
const eventEmitter = require('events').EventEmitter;
// the modules with all the member functions
const commandModule = require('./_commands');
const controlModule = require('./_controls');
const eventModule = require('./_events');
const informationModule = require('./_information');
const playlistModule = require('./_playlist');
const audioModule = require('./_audio');
const videoModule = require('./_video');
const subtitleModule = require('./_subtitle');
const startStopModule = require('./_startStop');
const ErrorHandler = require('../error');
const ipcInterface = require('../ipcInterface/ipcInterface');
const util = require('../util');
function mpv(options, mpv_args){
// intialize the event emitter
eventEmitter.call(this);
// merge the user input options with the default options
this.options = util.mergeDefaultOptions(options);
// get the arguments to start mpv with
this.mpv_arguments = util.mpvArguments(this.options, mpv_args);
// saves the IDs of observedProperties with their propertyname
// key: property
// value: id
this.observedProperties = {};
// timeposition of the current song
this.currentTimePos = null;
// states whether mpv is running or not
this.running = false;
// error handler
this.errorHandler = new ErrorHandler();
// set up the ipcInterface
this.socket = new ipcInterface(this.options);
}
mpv.prototype = Object.assign({
constructor: mpv,
// loads a file into mpv
// mode
// replace replace current video
// append append to playlist
// append-play append to playlist and play, if the playlist was empty
//
// options
// further options
load: async function(source, mode = 'replace', options) {
// check if this was called via load() or append() for error handling purposes
const caller = util.getCaller();
// reject if mpv is not running
if (!this.running){
throw (
this.errorHandler.errorMessage(8, caller, options ? [source, mode].concat(options) : [source, mode], null, {
'replace': 'Replace the currently playing title',
'append': 'Append the title to the playlist',
'append-play': 'Append the title and when it is the only title in the list start playback'
})
);
}
// reject the promise if the mode is not correct
if(!['replace', 'append', 'append-play'].includes(mode)){
throw (
this.errorHandler.errorMessage(1, caller, options ? [source, mode].concat(options) : [source, mode], null, {
'replace': 'Replace the currently playing title',
'append': 'Append the title to the playlist',
'append-play': 'Append the title and when it is the only title in the list start playback'
})
);
}
// MPV accepts various protocols, but the all start with <protocol>://, leave this input as it is
// if it's a file, transform the path into the absolute filepath, such that it can be played
// by any mpv instance, started in any working directory
// also checks if the protocol is supported by mpv and throws an error otherwise
const sourceProtocol = util.extractProtocolFromSource(source);
if (sourceProtocol && !util.validateProtocol(sourceProtocol)) {
throw (
this.errorHandler.errorMessage(
9,
caller,
options ? [source, mode].concat(options) : [source, mode],
null,
'See https://mpv.io/manual/stable/#protocols for supported protocols')
);
}
source = sourceProtocol ? source : path.resolve(source);
await new Promise ((resolve, reject) => {
// socket to observe the command
const observeSocket = net.Socket();
observeSocket.connect({path: this.options.socket}, async () =>{
// send the command to mpv
await this.command('loadfile', options ? [source, mode].concat(util.formatOptions(options)) : [source, mode]);
// get the playlist size
const playlistSize = await this.getPlaylistSize();
// if the mode is append resolve the promise because nothing
// will be output by the mpv player
// checking whether this source can be played or not is done when
// the source is played
if(mode === 'append'){
observeSocket.destroy();
resolve();
}
// if the mode is append-play and there are already songs in the playlist
// resolve the promise since nothing will be output
if(mode === 'append-play' && playlistSize > 1){
observeSocket.destroy();
resolve();
}
// timeout
let timeout = 0;
// check if the source was started
let started = false;
observeSocket.on('data', (data) => {
// increase timeout
timeout += 1;
// parse the messages from the socket
const messages = data.toString('utf-8').split('\n');
// check every message
messages.forEach((message) => {
// ignore empty messages
if(message.length > 0){
message = JSON.parse(message);
if('event' in message){
if(message.event === 'start-file'){
started = true;
}
// when the file has successfully been loaded resolve the promise
else if(message.event === 'file-loaded' && started){
observeSocket.destroy();
// resolve the promise
resolve();
}
// when the track has changed we don't need a seek event
else if (message.event === 'end-file' && started){
observeSocket.destroy();
reject(this.errorHandler.errorMessage(0, caller, [source]));
}
}
}
});
// reject the promise if it took to long until the playback-restart happens
// to prevent having sockets listening forever
if(timeout > 10){
observeSocket.destroy();
reject(this.errorHandler.errorMessage(5, caller, [source]));
}
});
});
});
}
// add all the other modules
}, audioModule,
controlModule,
commandModule,
eventModule,
informationModule,
playlistModule,
startStopModule,
subtitleModule,
videoModule,
// inherit from EventEmitter
eventEmitter.prototype);
// export the mpv class as the module
module.exports = mpv;
;