airplay 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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