j-holub / Node-MPV

A NodeJs Module for MPV Player
https://www.npmjs.com/package/node-mpv
MIT License
117 stars 73 forks source link

Not possible to setup a playlist from scratch (append) and play later? #64

Closed twilson90 closed 4 years ago

twilson90 commented 4 years ago

Bug Description

I'd like to set up a playlist without playing it immediately. But it seems I become a bit stuck when when I use mpv.load() with mode 'append' for the first item. There doesn't appear to be a way to load and play the first item afterwards. If I call mpv.getDuration(), I get an error 'property unavailable', so it's clear nothing is loaded.

mpv.play() doesn't do anything, setting the playlist-num to 0 results in an error, mpv.next() also gives an error.

After setting the playlist, presumably I can't use the load command with the first file as that would wipe the playlist.

I thought about using mode "replace" when loading the first playlist item and then quickly calling pause, but I don't want it to play for a millisecond.

It's very late in the evening and I'm a bit bleary eyed, so if the answer is painfully obvious I apologise.

EDIT: I should note I'm using the --o argument to output the video stream, I've just noticed that the behaviour is different without this argument. The playlist immediately plays and getDuration does not result in an error. I suspect there's more likely an issue with the way mpv handles playlists than the node-mpv wrapper.

Software Versions
j-holub commented 4 years ago

Hey there, thanks for filing an issue :)

You're not using a playlist file, right? Just consecutive calls of mpv.load(..., mode='append'), right? I will have a look at it :)

twilson90 commented 4 years ago

Correct, just individual files.

j-holub commented 4 years ago

I was just messing around with the player and to my surprise I cannot even load a file without playing it. Even if I load await mpv.load('somefile.mp3', mode='append' or await mpv.append('somefile.mp3') (which is essentially the same, append() calls load() with mode='append') the file is played. I went through the code and everything seems fine, but for some reason it always plays back to file.

This could indeed mean, that mpv itself starts the playback, not the node-mpv library. I will see if I can confirm that and if it's the case, what I can do about it. I could think of a workaround that if append() or load() is used to append to an empty playlist, that it is paused right away, in order to not start the playpack.

Anyways, good find. Thank you :)

AxelTerizaki commented 4 years ago

Maybe adding --pause option when loading the file would work? You'd have to use mpv.play or perhaps mpv.resume to then start playback.

j-holub commented 4 years ago

Good idea, I'll see if that resolves the problem. It's easy to check the size of the playlist to see if the user is appending to an empty playlist. That check is already done actually but it doesn't seem to be effective because mpv seems to start the playback anyways.

j-holub commented 4 years ago

I have done some digging. I start the player myself on the command line using mpv --idle --input-ipc-server=/tmp/mpv.sock (MacOS / Linux only, Windows uses \\\\.\\pipe\\mpvserver as the socket). Then use socat to send a command, this simulates what Node-MPV does, it sends commands according to mpv's (terrible) JSON API over a unix socket. I send the following command {"command": ["loadfile", "somefile.mp3", "append"]}, which is the exact same command Node-MPV should (or maybe does?) send if you call load('somefile.mp3', mode='append') or `append('somefile.mp3') and it adds the file to the playlist and does not play it, just as would be expected behaviour and as @hedgehog90 wants it to be.

So far so good, mpv is working as expected and the bug should be in my code. However, I wasn't able to actually play back the file using any commands sent via socat. What play() in Node-MPV does is setting the pause property to false. However, pause is already set to false, which means that sending the command {"command": ["set_property", "pause", false]} (mimicking what callingplay()` does), changes nothing and the file won't play.

I have dug through the mpv documentation and I don't find any command that enables us to play a file that is added to an empty playlist using append as the mode.

I think I will have to file an issue over at MPV GitHub, but unfortunately, my MacBook's battery is at 2% and my charger is broken. Tomorrow I guess.

tl;dr My code might have a bug sending the wrong command. If you really send the command with mode append to mpv it is added and not played. However, mpv does not seem to offer any possiblity to actually play that file afterwards. I will have to talk to the mpv guys directly.

j-holub commented 4 years ago

I was able to kind of fix my charger, the issue can be found here mpv-player/mpv#7525 , let's see what the mpv guys have to say about it.

j-holub commented 4 years ago

A very quick response to the my filed issue told me, that setting playlist-pos to 0 will play the file and it does. I have no idea why this works, and I do not find this intuitive at all, but it's a workaround I can probably implement into Node-MPV.

twilson90 commented 4 years ago

Excellent detective work, J! I figured there was a quirky solution like that. I've tested it and can confirm it works. I've replaced all instances of goto(i) with setProperty('playlist-pos', i) and it works perfectly. Much appreciated :)

j-holub commented 4 years ago

Glad I could be of help :)

Nice that you got a temporary workaround until I've coded a proper fix. Well, in my opinion, it's a fix to something that should have never been designed that way in the first place, but what can I do.

j-holub commented 4 years ago

Hey @hedgehog90, you said you were able to to append a file without it being played immediately, right? Because I cannot get it to work, even is I use append the file will always start to be played. But only if I send that command via NodeJs, it doesn't matter if I use my library or if I write directly to the socket, it always starts the playback. But if I send the command from the command line using socat and not NodeJs, append works as intended.

twilson90 commented 4 years ago

Here's what I experience...

If I start mpv using node-mpv without any input files, upon loading I get a playlist with 1 item, its filename is a string - "undefined". The property "pause" = false and "playlist-pos" = null

If I then append to the playlist, the new items do not play, presumably because mpv is stuck 'playing' this strange undefined item... but the 'playlist-pos' is said to be null (not 0). [EDIT: Just realised - this is because I call pause() immediately before loading my playlist items... If I don't call pause() my first appended item is played.]

Note: I am 'observing' all the properties mentioned. And I'm starting mpv without any additional arguments.

For some reason I've only started noticing this 'undefined' item today. I recently updated mpv from a version that was a couple months old, so maybe that explains it... or there is something else happening in my script that I'm unaware of.

That said, I've stripped my script back to its basics though and I still get this 'undefined' item whenever I start mpv. I've just been looking to see if I can prevent it from generating but I've had no luck.

twilson90 commented 4 years ago

^ just edited the above to correct a misapprehension.

Basically if you set pause to true immediately after starting mpv, then you should get the behaviour you'd expect.

j-holub commented 4 years ago

The thing is, I'm experiencing differnet behavour when I start mpv with Node-MPV and if I start it seperately and I have no idea.

Take the following code, that does not use my library at all. It's just NodeJS itself. Using child_process.spawn to start mpv in idle mode and net.socket to connect to that unix socket(remember it's a different socket on Windows) and send the message to load somesong.mp3. It works as expected, the song is NOT played but only appended. If I then set playlist-pos to 0 the song starts playing. This is expected behaviour and how I would want Node-MPV to behave.

const spawn = require('child_process').spawn;
const net = require('net');

const mpv = spawn('mpv', ['--idle', '--input-ipc-server=/tmp/node-mpv.sock', '--no-audio-display', '--no-video', '--msg-level=ipc=v', '--really-quiet']);

setTimeout(() => {
    const socket = new net.Socket();
    socket.connect({path: '/tmp/node-mpv.sock'}, () => {
        socket.write('{"command": ["loadfile", "somesong.mp3", "append"], "request_id": "dsdfsd"}\n')
    });
}, 1000);

However, if I use Node-MPV for this the song starts playing immediately. Even if I use the same code as above to send the loadfile command, bypassing Node-MPV. I think it has to have to something to do with the way Node-MPV starts mpv, but I use the exact same arguments. I spent hours on trying to get to the gist of the problem, trying every possible configuration I could think of. I just don't get it, I have no idea why the behaviour differs, because inside the library, Node-MPV calls the exact same commands and by using the argument --msg-level=cplayer=debug I found out, that the exact same messages arrive at the mpv side when loading the file. I just don't get it.

I might have to open an issue over at the mpv repository again, allthough this is cleary a problem on my side. But the guy maintaining mpv is super knowledgeable about mpv and might know what causes the problem.

For reference: mpv-player/mpv#7543

j-holub commented 4 years ago

It's finally fixed with the latest commit :)

The actual fix to this issue was pretty easy, but by debugging this I stumbled onto a way bigger issue, namely that mpv was in fact not fully loaded and ready once start() resolves.

Thanks for filing this issue, this helped out the project tremendously. Sorry it took so long.

rexn8r commented 2 years ago

hi @j-holub

i am trying to implement mpv node in electron app in similar fashion.

I want to play multiple video files in sequence. I can use the load method along with "stopped" event to move and play one file to next one. The trouble with this code is then when the next file is loaded, there is a black gap in between transition.

I wanted to use the load method with append flag but there is no way to intercept the file ended event i.e the 'stopped' or 'status' event doesn't provide information about when each file ended playing.

I want to detect individual file ended event so i can log some information in a text file i.e. proof of play.

I tried below code;

try{ await mpv.start(); await mpv.setMultipleProperties({ "border": "no", "geometry" : "1920x100+0+0", "keep-open":"always" }); await mpv.load(exePath + "\content\1-topbar\15.mp4"); await mpv.load(exePath + "\content\1-topbar\40.mp4", mode='append');

    //console.log(await mpv.getDuration());
    //console.log(await mpv.getProperty('someProperty'));
}
catch (error) {
    // Maybe the mpv player could not be started
    // Maybe the video file does not exist or couldn't be loaded
    // Maybe someProperty is not a valid property
    console.log(error);
}

mpv.on('status', (status) => { console.log(status) if(status.property =="pause" && status.value==true){ console.log("paused"); mpv.next(); ----->> this does move to next file mpv.resume(); //mpv.play() ------>> but file doesn't play as mpv is in paused state } });

The "keep-open":"always" returns "pause" property when current file is ended. but the Next() as well as Play/Resume() functions doesn't start the next file.

can you please help resolve this issue?

thanks rex