elcuervo / airplay

Airplay bindings to Ruby
MIT License
1.07k stars 69 forks source link

Apple TV returns to home screen between images when device has password #10

Closed sodabrew closed 12 years ago

sodabrew commented 12 years ago

My ATV 2, with latest iOS 5, returns to the home screen in between updates. I haven't started diagnosing what's up -- maybe I'm losing my connection between send_image / send_video calls? Opening this issue in case you've debugged this before.

Steps:

  airplay = Airplay::Client.new
  airplay.use apple_tv_name
  airplay.password apple_tv_password
  airplay.send_image(url1)
  airplay.send_image(url2)

Expected: Apple TV displays url1, then displays url2 without interruption.

Actual: Apple TV displays url2, then home screen, then url2.

elcuervo commented 12 years ago

Running this i'm not having any issues, are you keeping the client alive?

require 'airplay'

airplay = Airplay::Client.new
airplay.send_image("http://www.prelovac.com/vladimir/wp-content/uploads/2008/03/example.jpg")
airplay.send_image("http://home.comcast.net/~urbanjost/images/globe_west_2048.jpg", :slide_left)
sleep(3) # If the client ends the connection ends
sodabrew commented 12 years ago

Updated above: added airplay.password call, as I have a password on my ATV.

Wiresharked the connection, and I see that every PUT of a new image is refused with a 401 Unauthorized and a new connection is established and the password sent on the first call through new connection.

I am using sleep between images in my script. Also per the wireshark protocol dump, it looks like airplay is sending a keep alive time of 30 seconds by default -- which means I should be sending a command within that window. Is there a ping command I can make sure to call every so often, in case I want to keep the same image on screen longer?

Steps:

     airplay = Airplay::Client.new
     airplay.use apple_tv_name
     airplay.password apple_tv_password

     airplay.send_image(url1)
     sleep 3
     # image 1 is on the screen for 3 seconds

     airplay.send_image(url2)
     # at this point, the ATV goes back to home screen, then switches to image 2
     sleep 3
     # image 2 is on the screen for 3 seconds
elcuervo commented 12 years ago

Oh… that's great feedback, i'll take a look at that

elCuervo http://elcuervo.co http://github.com/elcuervo

On Wednesday, March 21, 2012 at 5:59 PM, Aaron Stone wrote:

Updated above: added airplay.password call, as I have a password on my ATV.

Wiresharked the connection, and I see that every PUT of a new image is refused with a 401 Unauthorized and a new connection is established and the password sent on the first call through new connection.


Reply to this email directly or view it on GitHub: https://github.com/elcuervo/airplay/issues/10#issuecomment-4627058

sodabrew commented 12 years ago

First attempt, just reuse the previous authorization. This doesn't seem to work.

diff --git a/lib/airplay/protocol.rb b/lib/airplay/protocol.rb
index e20b6c3..d4cc1d7 100644
--- a/lib/airplay/protocol.rb
+++ b/lib/airplay/protocol.rb
@@ -8,6 +8,7 @@ class Airplay::Protocol
   def initialize(host, port, password)
     @device = { :host => host, :port => port }
     @password = password || '' # Passing nil breaks Net::HTTP::DigestAuth
+    @authentication = false # after a www-authenticate challenge, keep the digest for the next query
     @http = Net::HTTP::Persistent.new
     @http.debug_output = $stdout if ENV.has_key?('HTTP_DEBUG')
   end
@@ -17,11 +18,19 @@ class Airplay::Protocol
     uri.user = "Airplay"
     uri.password = @password

+    # If there was previously a www-authenticate field, use it again
+    if @authentication
+      request.add_field 'Authorization', @authentication
+    end
+
     response = @http.request(uri, request) {}
+
+    # Either it's our first time authenticating with this device, so we have to reply with a digest
+    # based on the previous challenge, or we used our previous challenge and the device wants a new one.
     if response['www-authenticate']
       digest_auth = Net::HTTP::DigestAuth.new
-      authentication = digest_auth.auth_header uri, response['www-authenticate'], request.method
-      request.add_field 'Authorization', authentication
+      @authentication = digest_auth.auth_header uri, response['www-authenticate'], request.method
+      request.add_field 'Authorization', @authentication
       response = @http.request(uri, request) {}
     end
sodabrew commented 12 years ago

Ok, while I can reproduce the problem just with two send_image calls, upon code inspection I think there's a clear problem with mixed media calls.

The issue is in client.rb, where each of send_image, send_audio, send_video, and scrub create new connections:

 def handler
     Airplay::Protocol.new(@active_server.ip, @active_server.port, @password)
 end

 def send_image ...
     @image_proxy ||= Airplay::Protocol::Image.new(handler)
     ...

 def send_audio ...
     @media_proxy ||= Airplay::Protocol::Image.new(handler)
     ...

Note that handler always returns a new connection. So you're going to get one connection in @image_proxy, one in @media_proxy, and one more in @scrub_proxy. The docs for Net::HTTP::Persistent don't indicate that it would reuse the open socket to the server. It's not clear how the ATV handles multiple connections, but I wonder if it doesn't like it.

sodabrew commented 12 years ago

Ok, found it. Problem is here: https://github.com/drbrain/net-http-persistent/blob/master/lib/net/http/persistent.rb#L230

Net::HTTP::Persistent is dropping and restarting connections after 5 seconds.

The code above with sleep 3 DOES work. Raise the sleep times above 5, and then it DOESN'T work!

You can cherry-pick the changes above if you don't want to pull the rest of my fork.

sodabrew commented 12 years ago

nil doesn't work. Submitted patch to Net::HTTP::Timeout: https://github.com/drbrain/net-http-persistent/pull/21

In the mean time, changed to 900 seconds: https://github.com/sodabrew/airplay/commit/abfbbec554e93b5fed17a331a4ebd5dd08abffa9

elcuervo commented 12 years ago

Fixed in 3b86cf38bcbac9c4298a697849f2c4cb79f5c18c.