evandcoleman / node-appletv

A node module for interacting with an Apple TV (4th-generation or later) over the Media Remote Protocol.
https://evandcoleman.github.io/node-appletv/
MIT License
180 stars 48 forks source link

Laggy responses #22

Open mschwartz opened 5 years ago

mschwartz commented 5 years ago

I read your Medium article and you briefly touched on the issue of messages coming much later than actual events (like pressing pause on the remote and being notified of it), and so on.

Is it possible you are queuing up messages in protocol buffer state (or something like that) that aren't processed as received (but really are received)?

If my guess is right, only upon next message filling up the input buffers more will cause the prior event to be "received" and this would be much later than expected.

The play/pause/stopped state is completely unreliable for me.

anurag426 commented 5 years ago

Is select command working at your end

mschwartz commented 5 years ago

select command?

I am able to play/pause/etc. using the API. The incoming is the issue.

The python version is much more responsive, but doesn't provide all the same information.

anurag426 commented 5 years ago

Select command means are you able to select and video or run any application ?

mschwartz commented 5 years ago

Yes. I can use the API to simulate the button presses. So I can start video playing.

If I stop the video, I frequently see no now playing message to show the video paused or stopped. If I pause the video, I sometimes get now playing message with paused state. I frequently see it saying playing state when paused or stopped, or paused state when playing.

Sometimes it is completely inverse state. Paused while the video is playing, playing when the video is paused.

The state changed messages come in many seconds after I change play or pause of the video using my physical remote.

With pyatv, the messages are immediate and correct.

Apple TV 4K model.

david-massey commented 5 years ago

@mschwartz Are you using the CLI, or are you integrating this project into another app? I am integrating this project into another app, so this might not apply to you.

I was receiving both a lot of duplicated now playing messages and some old now playing messages. Both issues were on the AppleTV and not this project.

I now have all responses snappy, correct, and the right count/debounced.

Old messages:

node-appletv automatically requests now playing when it connects. If the connection breaks before AppleTV sends the requested update, AppleTV will send it again when the app connects. This can probably be generalized to any requests if the connection breaks before the response is successfully sent from AppleTV.

Explicit Example:

node-appletv connects, node-appletv automatically requests now playing information, an uncaught code error halts the app and the connection breaks, the dev fixes the error. node-appletv connects, node-appletv requests now playing, AppleTV sends 2 messages back -- the first of which is old information.

Solution:

Fix/catch errors & keep the connection open.

Multiple messages:

I have this backing a webapp with some simple controls. While I was making the wrapper for the interface, I went through the pairing process multiple times. Every time I paired it, it added a device to my AppleTV's known/paired devices list; this caused me to receive now playing information from the AppleTV for each time the app was paired.

Example:

I went through the pairing process 5 times; every time now playing updated, AppleTV sent 5 now playing updates to the app.

Solution:

I unpaired all extra devices on the AppleTV.

mschwartz commented 5 years ago

I have this running in an app, though I've tried the CLI and it exhibits the same issues.

I've been messing with node-pyatv and it is spot on all the time, really accurate. But it lacks some of the now playing info.

This module would benefit from internally connecting every second or two, just to get constant stream of now playing messages. My use of this module has to use setInterval(... 1 second) to send a position update (elapsedTime) message to the clients (or I can do that kind of thing on the client). I mean, you want to render a slider where the knob moves as the video or music plays, no?

david-massey commented 5 years ago

Mine stays connected to the AppleTV. It does not receive playing feedback every second as the elapsed time updates, but whenever the (playbackState | appDisplayName | artist | album | title) changes, I get notified/updated (accurately) in less than a second. I am not polling the device at all.

The default settings for updates are:

// dist/lib/appletv.js ~line 135

nowPlayingUpdates: true,
artworkUpdates: true,
keyboardUpdates: false,
volumeUpdates: false

If you're willing to share your code, I can take a look at it and see if I see the same results as you.

mschwartz commented 5 years ago

I must not have been clear.

In my server code, when I receive a nowPlaying with playing state, I start a setInterval to count up elapsedTime. There are few updates of elapsed time from the ATV.

I suggested polling because you do get an update on each connect.

My client frequently says now playing and the elapsed time increments indefinitely. The ATV might not even be powered on. Or I use HOME to leave an app while it’s playing something.

mschwartz commented 5 years ago

Link to code

https://github.com/RoboDomo/appletv-microservice/blob/master/index.js

david-massey commented 5 years ago

The suggestion for a poller so that you don't have to set an interval to update the elapsed time is clear. I am trying to address "Laggy responses" and not "Feature Request: Add poller to update elapsed play time".

The original issue states "messages coming much later than actual events" and "The play/pause/stopped state is completely unreliable for me." -- these are things I do not see in the implementation I'm working on. Thanks for sharing your code; I'll take a look.

Side note: I am using this version (pull request #3), for I develop on mac and could not get this module to install (#15). There are some differences.

david-massey commented 5 years ago

I noticed you're currently mostly handling/parsing onMessage and not much with onNowPlaying. Did you find that this yielded better, more predictable behavior than just handling onNowPlaying? Currently, I'm just handling onNowPlaying, and everything is snappy and accurate.

There's a chance I missed something that is watching this.state and maybe publishing/MQTT changes as they occur, but it appears you're overwriting this.state throughout the parsing/handling functions instead of changing the values of particular keys/elements of it. Forgive me if I misread that. But, in your currently functioning version (the if (true { ... }'s referenced above): Line 130 nulls out all values (should probably come before the await openConnection(credentials)) Line 154 Line 169 Line 173 Line 208 Line 230 Line 242 and maybe a couple others. When handling both onMessage and onNowPlaying, you are dealing with async callbacks that have the ability to rewrite/modify an instance variable asynchronously...

But more importantly, there is line 101 -- the interval that every second is setting this.state = { elapsedTime: elapsed }. Do you mean to say this.state.elapsedTime = elapsed?

Because your parsing functions check various values of this.state as it's updating it, I am not sure if it's doing what's intended with all the overwrites instead of modifications.

mschwartz commented 5 years ago

There is a setter for state that does Object.assign(). Only changed members are sent via MQTT.

A elapsedTime topic is sent 1/second.

If you notice the if true/false around various code segments, I was trying anything I could to deduce what is going on.

I do have more than one ATV, and I notice it seems wrong devices appear for each, one IPv4 and one v6.

david-massey commented 5 years ago

I figured I probably missed something like that; my apologies.

mschwartz commented 5 years ago

React-like setState() implemented as a setter.

david-massey commented 5 years ago

I'll play around with it tomorrow, but I've switched it back to the solo onNowPlaying instead of handling both onMessage and onNowPlaying and commented out a couple missing things (like config and HostBase), but I'm not seeing laggy responses or incorrect states at the moment. Switching apps, play/pause -- everything is lining up exactly and instant.

I'm on node v10.15.3, using GioCirque's branch/pull request, and 1 single AppleTV (4k, homesharing is currently on as my coworker is working with pyatv at the same time, but I've tested the library -- not your code -- with homesharing off). I'll keep you posted after I turn homesharing off tomorrow.

mschwartz commented 5 years ago

I have home sharing on. The only way to get iTunes to automatically play the next video/show is to make a playlist and play from a network computer on the ATV.

Thanks for your time.

david-massey commented 5 years ago

Yeah, that part really sucks about aTV and purchased content; netflix/hulu certainly work better, there. Sorry I couldn't help more. If I find anything as I stress test, I'll be sure to post back.

mschwartz commented 5 years ago

I don’t see anything in GioCirque’s fork that should make a difference.

david-massey commented 5 years ago

Neither did I -- why I was comfortable trying to look/help. But I need the package updates to install the module.

mschwartz commented 5 years ago

Haha.

Ok, so one more thing.

On my harmony remote, the Apple TV control has a reboot button. It does reboot the ATV , so there is some over the air command to make it reboot.

I tried every combination of command values, but could not get it to reboot.

Any ideas?

david-massey commented 5 years ago

Other than man in the middle it, no. But I just started working with aTV this weekend.

david-massey commented 5 years ago

So I don't know what to say. Yesterday everything was very snappy and correct. This morning, a coworker developed more with pyatv (the only real change that's happened), and today everything is very slow and unreliable. It takes 5-35s each play state change before node-appletv receives it from the AppleTV. I checked this by adding some log statements into node-appletv's connection's onData (to timestamp when it receives data/slices the buffer/executes the callback), and everything internal is snappy; the appleTV is now very slow to update node-appletv, but it's very fast to update my coworker's pyatv service. This is still the case if he spins down his service. It persists after restarting the AppleTV.

mschwartz commented 5 years ago

So you're seeing what I see now.

I'm currently working with node-pyatv instead of node-appletv in a test branch. The pyatv code works, but doesn't have all the info from nowPlaying that node-appletv has.

I was seeing the slowness and unreliable behavior before I installed pyatv. That said, when I pair with pyatv code, I get like 8x pyatv devices in the settings -< remote applications screen.

mschwartz commented 5 years ago

I made a minimal test program:

const { AppleTV, scan, parseCredentials } = require("node-appletv");
const config = require("./config");

console.log("credentials", config, config.atv[0].creds);
let credentials = parseCredentials(config.atv[0].creds);

const main = async () => {
  const devices = await scan(credentials.uniqueIdentifier);
  const device = await devices[0].openConnection(credentials);
  console.log("connected");
  //  device.on("debug", message => console.log("debug", message));
  device.on("nowPlaying", message => console.log("message", message));
};
main();

The longer it runs, as I'm using menu, play, pause, etc., on the remote, the more out of sync and laggy it gets.

CooperCGN commented 4 years ago

I just stumbled over this post coming from https://github.com/lukasroegner/homebridge-apple-tv-remote which uses node-appletv. I see the same behavior with that homebridge plugin. The more often I press play/pause on the physical remote the laggier that plugin reflects the correct state while the remote widget on any of the associated iOS devices reacts instantly. Any chance to see a fix in the future?

readeral commented 4 years ago

Can I just chime in here and say that I actually get this response even from my genuine Apple TV remote using the Netflix app, and I wonder if in part is an issue of AppleTV developers rather than much to do with this library?