airplay 0.2.6 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
+ before_install: sudo apt-get install -y libavahi-compat-libdnssd-dev
1
2
  rvm:
2
3
  - 1.8.7
3
4
  - 1.9.2
4
5
  - 1.9.3
5
- before_script: sudo apt-get install -y libavahi-compat-libdnssd-dev && bundle install
6
+ - jruby
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Travis](https://secure.travis-ci.org/elcuervo/airplay.png)
4
4
 
5
- ![Ruby Airplay](https://github.com/elcuervo/elcuervo.github.com/raw/master/images/posts/airplay/ruby_airplay.png)
5
+ ![Ruby Airplay](http://elcuervo.co/images/posts/airplay/ruby_airplay.png?1)
6
6
 
7
7
  A client (and someday a server) of the superfancy http content stream technique
8
8
  that Apple uses in its products.
@@ -1,20 +1,20 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "airplay"
3
- s.version = "0.2.6"
3
+ s.version = "0.2.8"
4
4
  s.summary = "Airplay client"
5
5
  s.description = "Send image/video to an airplay enabled device"
6
6
  s.authors = ["elcuervo"]
7
7
  s.email = ["yo@brunoaguirre.com"]
8
8
  s.homepage = "http://github.com/elcuervo/airplay"
9
9
  s.files = `git ls-files`.split("\n")
10
- s.test_files = `git ls-files spec`.split("\n")
10
+ s.test_files = `git ls-files test`.split("\n")
11
11
 
12
- s.add_dependency("dnssd")
13
- s.add_dependency("net-http-persistent")
14
- s.add_dependency("net-http-digest_auth")
12
+ s.add_dependency("dnssd", "~> 2.0")
13
+ s.add_dependency("net-http-persistent", "~> 2.5")
14
+ s.add_dependency("net-http-digest_auth", "~> 1.2")
15
15
 
16
- s.add_development_dependency("cutest")
17
- s.add_development_dependency("capybara")
18
- s.add_development_dependency("fakeweb")
19
- s.add_development_dependency("vcr")
16
+ s.add_development_dependency("cutest", "~> 1.1")
17
+ s.add_development_dependency("capybara", "~> 1.0")
18
+ s.add_development_dependency("fakeweb", "~> 1.3")
19
+ s.add_development_dependency("vcr", "~> 2.0")
20
20
  end
@@ -1,5 +1,5 @@
1
1
  class Airplay::Client
2
- attr_reader :servers, :active_server, :password
2
+ attr_reader :servers, :active, :password
3
3
 
4
4
  def initialize(server = false, server_browser = Airplay::Server::Browser)
5
5
  @server_browser = server_browser
@@ -8,7 +8,11 @@ class Airplay::Client
8
8
  end
9
9
 
10
10
  def use(server)
11
- @active_server = server.is_a?(Airplay::Server::Node) ? server : @server_browser.find_by_name(server)
11
+ @active = if server.is_a?(Airplay::Server::Node)
12
+ server
13
+ else
14
+ @server_browser.find_by_name(server)
15
+ end
12
16
  end
13
17
 
14
18
  def password(password)
@@ -24,7 +28,7 @@ class Airplay::Client
24
28
  end
25
29
 
26
30
  def handler
27
- Airplay::Protocol.new(@active_server.ip, @active_server.port, @password)
31
+ @_handler ||= Airplay::Protocol.new(@active.ip, @active.port, @password)
28
32
  end
29
33
 
30
34
  def send_image(image, transition = :none)
@@ -8,45 +8,65 @@ class Airplay::Protocol
8
8
  def initialize(host, port, password)
9
9
  @device = { :host => host, :port => port }
10
10
  @password = password
11
+ @authentications = {}
11
12
  @http = Net::HTTP::Persistent.new
13
+ @http.idle_timeout = 900 # until nil works
12
14
  @http.debug_output = $stdout if ENV.has_key?('HTTP_DEBUG')
13
15
  end
14
16
 
15
- def make_request(request)
16
- uri = URI.parse "http://#{@device.fetch(:host)}:#{@device.fetch(:port)}#{request.path}"
17
- uri.user = "Airplay"
18
- uri.password = @password
17
+ def put(resource, body = nil, headers = {})
18
+ @request = Net::HTTP::Put.new resource
19
+ @request.body = body
20
+ @request.initialize_http_header DEFAULT_HEADERS.merge(headers)
21
+ make_request
22
+ end
19
23
 
20
- response = @http.request(uri, request) {}
21
- if response['www-authenticate']
22
- digest_auth = Net::HTTP::DigestAuth.new
23
- authentication = digest_auth.auth_header uri, response['www-authenticate'], request.method
24
- request.add_field 'Authorization', authentication
25
- response = @http.request(uri, request) {}
26
- end
24
+ def post(resource, body = nil, headers = {})
25
+ @request = Net::HTTP::Post.new resource
26
+ @request.body = body
27
+ @request.initialize_http_header DEFAULT_HEADERS.merge(headers)
28
+ make_request
29
+ end
30
+
31
+ def get(resource, headers = {})
32
+ @request = Net::HTTP::Get.new resource
33
+ @request.initialize_http_header DEFAULT_HEADERS.merge(headers)
34
+ make_request
35
+ end
36
+
37
+ private
38
+
39
+ def make_request
40
+ path = "http://#{@device[:host]}:#{@device[:port]}#{@request.path}"
41
+ @uri = URI.parse(path)
42
+ @uri.user = "Airplay"
43
+ @uri.password = @password
44
+
45
+ add_auth_if_needed
46
+
47
+ response = @http.request(@uri, @request) {}
27
48
 
28
49
  raise Airplay::Protocol::InvalidRequestError if response.code == "404"
29
50
  response.body
30
51
  end
31
52
 
32
- def put(resource, body = nil, headers = {})
33
- request = Net::HTTP::Put.new resource
34
- request.body = body
35
- request.initialize_http_header DEFAULT_HEADERS.merge(headers)
36
- make_request(request)
53
+ def add_auth_if_needed
54
+ if @password
55
+ authenticate
56
+ @request.add_field('Authorization', @authentications[@uri.path])
57
+ end
37
58
  end
38
59
 
39
- def post(resource, body = nil, headers = {})
40
- request = Net::HTTP::Post.new resource
41
- request.body = body
42
- request.initialize_http_header DEFAULT_HEADERS.merge(headers)
43
- make_request(request)
60
+ def authenticate
61
+ response = @http.request(@uri, @request) {}
62
+ auth = response['www-authenticate']
63
+ digest_authentication(auth) if auth
44
64
  end
45
65
 
46
- def get(resource, headers = {})
47
- request = Net::HTTP::Get.new resource
48
- request.initialize_http_header DEFAULT_HEADERS.merge(headers)
49
- make_request(request)
66
+ def digest_authentication(auth)
67
+ digest = Net::HTTP::DigestAuth.new
68
+ @authentications[@uri.path] ||=
69
+ digest.auth_header(@uri, auth, @request.method)
50
70
  end
51
71
 
52
72
  end
@@ -22,18 +22,17 @@ class Airplay::Protocol::Image
22
22
  end
23
23
 
24
24
  def send(image, transition = :none)
25
- image = URI.parse(image) if !(image =~ URI::regexp).nil?
25
+ image = URI.parse(image) if !!(image =~ URI::regexp)
26
26
  content = case image
27
27
  when String
28
- if File.exists?(image)
29
- File.read(image)
28
+ File.exists?(image) ? File.read(image) : image
29
+ when URI::HTTP then Net::HTTP.get(image)
30
+ else
31
+ if image.respond_to?(:read)
32
+ image.read
30
33
  else
31
- image
34
+ throw Airplay::Protocol::InvalidMediaError
32
35
  end
33
- when File
34
- image.read
35
- when URI::HTTP
36
- Net::HTTP.get(image)
37
36
  end
38
37
 
39
38
  @http.put(resource, content, transition_header(transition))
@@ -12,18 +12,18 @@ module Airplay::Server::Browser
12
12
  def self.browse
13
13
  @servers = []
14
14
  timeout 3 do
15
- DNSSD.browse!(Airplay::Protocol::SEARCH) do |reply|
15
+ DNSSD.browse!(Airplay::Protocol::SEARCH) do |node|
16
16
  resolver = DNSSD::Service.new
17
17
  target, port = nil
18
- resolver.resolve(reply) do |resolved|
18
+ resolver.resolve(node) do |resolved|
19
19
  port = resolved.port
20
20
  target = resolved.target
21
21
  break unless resolved.flags.more_coming?
22
22
  end
23
23
  info = Socket.getaddrinfo(target, nil, Socket::AF_INET)
24
- ip_address = info[0][2]
25
- @servers << Airplay::Server::Node.new(reply.name, reply.domain, ip_address, port)
26
- break unless reply.flags.more_coming?
24
+ ip = info[0][2]
25
+ @servers << Airplay::Server::Node.new(node.name, node.domain, ip, port)
26
+ break unless node.flags.more_coming?
27
27
  end
28
28
  end
29
29
  rescue Timeout::Error
@@ -2,7 +2,7 @@ require File.expand_path("helper", File.dirname(__FILE__))
2
2
 
3
3
  scope do
4
4
  test "connect to an authenticated source" do
5
- VCR.use_cassette("authenticate all the things!") do
5
+ with_cassette("authenticate all the things!") do
6
6
  airplay = Airplay::Client.new(false, MockedBrowser)
7
7
  airplay.password("password")
8
8
 
@@ -21,7 +21,7 @@ scope do
21
21
  end
22
22
 
23
23
  test "autoselect if only one server available" do
24
- assert_equal "Mock TV", @airplay.active_server.name
24
+ assert_equal "Mock TV", @airplay.active.name
25
25
  end
26
26
 
27
27
  end
@@ -1,135 +1,139 @@
1
- ---
2
- - !ruby/struct:VCR::HTTPInteraction
3
- request: !ruby/struct:VCR::Request
4
- method: :get
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
5
  uri: http://mocktv.local:7000/scrub
6
- body: !!null
7
- headers:
8
- user-agent:
6
+ body:
7
+ encoding: US-ASCII
8
+ base64_string: ""
9
+ headers:
10
+ user-agent:
9
11
  - MediaControl/1.0
10
- content-type:
12
+ content-type:
11
13
  - text/x-apple-plist+xml
12
- connection:
14
+ connection:
13
15
  - keep-alive
14
- keep-alive:
16
+ keep-alive:
15
17
  - 30
16
- response: !ruby/struct:VCR::Response
17
- status: !ruby/struct:VCR::ResponseStatus
18
+ response:
19
+ status:
18
20
  code: 401
19
21
  message: Unauthorized
20
- headers:
21
- date:
22
- - Thu, 13 Oct 2011 12:22:53 GMT
23
- content-length:
24
- - '0'
25
- www-authenticate:
26
- - Digest realm="AirPlay", nonce="MTMxODUwODU3MyAnPHQuK0fHAbH43SnwM2zd"
27
- body: !!null
28
- http_version: '1.1'
29
- ignored: false
30
- - !ruby/struct:VCR::HTTPInteraction
31
- request: !ruby/struct:VCR::Request
32
- method: :get
22
+ headers:
23
+ date:
24
+ - Fri, 23 Mar 2012 22:10:30 GMT
25
+ content-length:
26
+ - "0"
27
+ www-authenticate:
28
+ - Digest realm="AirPlay", nonce="MTMzMjU0MDYzMCArTzzIA84dWO8ijTtj5Rhw"
29
+ body:
30
+ encoding: US-ASCII
31
+ base64_string: ""
32
+ http_version: "1.1"
33
+ recorded_at: Fri, 23 Mar 2012 22:10:30 GMT
34
+ - request:
35
+ method: get
33
36
  uri: http://mocktv.local:7000/scrub
34
- body: !!null
35
- headers:
36
- user-agent:
37
+ body:
38
+ encoding: US-ASCII
39
+ base64_string: ""
40
+ headers:
41
+ user-agent:
37
42
  - MediaControl/1.0
38
- content-type:
43
+ content-type:
39
44
  - text/x-apple-plist+xml
40
- connection:
45
+ connection:
41
46
  - keep-alive
42
- - keep-alive
43
- keep-alive:
44
- - 30
47
+ keep-alive:
45
48
  - 30
46
- host:
49
+ host:
47
50
  - mocktv.local:7000
48
- authorization:
49
- - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMxODUwODU3MyAnPHQuK0fHAbH43SnwM2zd",
50
- nc=00000000, cnonce="c2fc089b2e65ab801efe19f6c0748545", response="f90f23f345d03fb5e4ec4bd3e054731a"
51
- response: !ruby/struct:VCR::Response
52
- status: !ruby/struct:VCR::ResponseStatus
51
+ authorization:
52
+ - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMzMjU0MDYzMCArTzzIA84dWO8ijTtj5Rhw", nc=00000000, cnonce="0f55c9413ac973d5963a73572df0805b", response="e6458b63b53f22522a23138c94a5b02b"
53
+ response:
54
+ status:
53
55
  code: 200
54
56
  message: OK
55
- headers:
56
- date:
57
- - Thu, 13 Oct 2011 12:22:58 GMT
58
- content-type:
57
+ headers:
58
+ date:
59
+ - Fri, 23 Mar 2012 22:10:35 GMT
60
+ content-type:
59
61
  - text/parameters
60
- content-length:
61
- - '38'
62
- body: ! 'duration: 0.000000
63
-
64
- position: 0.000000
62
+ content-length:
63
+ - "38"
64
+ body:
65
+ encoding: US-ASCII
66
+ base64_string: |
67
+ ZHVyYXRpb246IDAuMDAwMDAwCnBvc2l0aW9uOiAwLjAwMDAwMAo=
65
68
 
66
- '
67
- http_version: '1.1'
68
- ignored: false
69
- - !ruby/struct:VCR::HTTPInteraction
70
- request: !ruby/struct:VCR::Request
71
- method: :get
69
+ http_version: "1.1"
70
+ recorded_at: Fri, 23 Mar 2012 22:10:35 GMT
71
+ - request:
72
+ method: get
72
73
  uri: http://mocktv.local:7000/scrub
73
- body: !!null
74
- headers:
75
- user-agent:
74
+ body:
75
+ encoding: US-ASCII
76
+ base64_string: ""
77
+ headers:
78
+ user-agent:
76
79
  - MediaControl/1.0
77
- content-type:
80
+ content-type:
78
81
  - text/x-apple-plist+xml
79
- connection:
82
+ connection:
80
83
  - keep-alive
81
- keep-alive:
84
+ keep-alive:
82
85
  - 30
83
- response: !ruby/struct:VCR::Response
84
- status: !ruby/struct:VCR::ResponseStatus
86
+ response:
87
+ status:
85
88
  code: 401
86
89
  message: Unauthorized
87
- headers:
88
- date:
89
- - Thu, 13 Oct 2011 12:23:03 GMT
90
- content-length:
91
- - '0'
92
- www-authenticate:
93
- - Digest realm="AirPlay", nonce="MTMxODUwODU4MyAx+UiV/7xCkP81VM4Y1S/G"
94
- body: !!null
95
- http_version: '1.1'
96
- ignored: false
97
- - !ruby/struct:VCR::HTTPInteraction
98
- request: !ruby/struct:VCR::Request
99
- method: :get
90
+ headers:
91
+ date:
92
+ - Fri, 23 Mar 2012 22:10:40 GMT
93
+ content-length:
94
+ - "0"
95
+ www-authenticate:
96
+ - Digest realm="AirPlay", nonce="MTMzMjU0MDY0MCCM+VRd9bJqrocG4oDxuVOU"
97
+ body:
98
+ encoding: US-ASCII
99
+ base64_string: ""
100
+ http_version: "1.1"
101
+ recorded_at: Fri, 23 Mar 2012 22:10:40 GMT
102
+ - request:
103
+ method: get
100
104
  uri: http://mocktv.local:7000/scrub
101
- body: !!null
102
- headers:
103
- user-agent:
105
+ body:
106
+ encoding: US-ASCII
107
+ base64_string: ""
108
+ headers:
109
+ user-agent:
104
110
  - MediaControl/1.0
105
- content-type:
111
+ content-type:
106
112
  - text/x-apple-plist+xml
107
- connection:
113
+ connection:
108
114
  - keep-alive
109
- - keep-alive
110
- keep-alive:
111
- - 30
115
+ keep-alive:
112
116
  - 30
113
- host:
117
+ host:
114
118
  - mocktv.local:7000
115
- authorization:
116
- - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMxODUwODU4MyAx+UiV/7xCkP81VM4Y1S/G",
117
- nc=00000000, cnonce="03d64d4bfe4b68f371eef278bea6efd1", response="de139d04d199649e1d4e9e9d96ec3f80"
118
- response: !ruby/struct:VCR::Response
119
- status: !ruby/struct:VCR::ResponseStatus
119
+ authorization:
120
+ - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMzMjU0MDYzMCArTzzIA84dWO8ijTtj5Rhw", nc=00000000, cnonce="0f55c9413ac973d5963a73572df0805b", response="e6458b63b53f22522a23138c94a5b02b"
121
+ response:
122
+ status:
120
123
  code: 200
121
124
  message: OK
122
- headers:
123
- date:
124
- - Thu, 13 Oct 2011 12:23:08 GMT
125
- content-type:
125
+ headers:
126
+ date:
127
+ - Fri, 23 Mar 2012 22:10:45 GMT
128
+ content-type:
126
129
  - text/parameters
127
- content-length:
128
- - '38'
129
- body: ! 'duration: 0.000000
130
-
131
- position: 0.000000
130
+ content-length:
131
+ - "38"
132
+ body:
133
+ encoding: US-ASCII
134
+ base64_string: |
135
+ ZHVyYXRpb246IDAuMDAwMDAwCnBvc2l0aW9uOiAwLjAwMDAwMAo=
132
136
 
133
- '
134
- http_version: '1.1'
135
- ignored: false
137
+ http_version: "1.1"
138
+ recorded_at: Fri, 23 Mar 2012 22:10:45 GMT
139
+ recorded_with: VCR 2.0.0