airplay 0.2.9 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/Gemfile +5 -0
  2. data/Gemfile.lock +68 -0
  3. data/HUGS +15 -0
  4. data/LICENSE +20 -0
  5. data/README.md +50 -72
  6. data/Rakefile +6 -9
  7. data/SPEC.md +34 -0
  8. data/airplay.gemspec +28 -16
  9. data/bin/air +27 -0
  10. data/examples/demo.rb +35 -0
  11. data/examples/image.rb +9 -0
  12. data/examples/video.rb +24 -0
  13. data/lib/airplay.rb +38 -17
  14. data/lib/airplay/browser.rb +68 -0
  15. data/lib/airplay/cli.rb +104 -0
  16. data/lib/airplay/configuration.rb +29 -0
  17. data/lib/airplay/connection.rb +114 -0
  18. data/lib/airplay/connection/authentication.rb +37 -0
  19. data/lib/airplay/connection/persistent.rb +44 -0
  20. data/lib/airplay/device.rb +81 -0
  21. data/lib/airplay/device/features.rb +43 -0
  22. data/lib/airplay/device/info.rb +22 -0
  23. data/lib/airplay/devices.rb +46 -0
  24. data/lib/airplay/logger.rb +20 -0
  25. data/lib/airplay/playable.rb +24 -0
  26. data/lib/airplay/protocol.rb +9 -72
  27. data/lib/airplay/protocol/app.rb +52 -0
  28. data/lib/airplay/protocol/message.rb +8 -0
  29. data/lib/airplay/protocol/playback_info.rb +49 -0
  30. data/lib/airplay/protocol/player.rb +170 -0
  31. data/lib/airplay/protocol/reverse.rb +69 -0
  32. data/lib/airplay/protocol/slideshow.rb +66 -0
  33. data/lib/airplay/protocol/timers.rb +25 -0
  34. data/lib/airplay/protocol/viewer.rb +56 -0
  35. data/lib/airplay/structure.rb +7 -0
  36. data/lib/airplay/viewable.rb +16 -0
  37. data/test/fixtures/cassettes/airplay/listing_slideshow_features.yml +201 -0
  38. data/test/fixtures/cassettes/airplay/play_an_entire_video.yml +100 -0
  39. data/test/fixtures/cassettes/airplay/sending_a_video.yml +71 -0
  40. data/test/fixtures/cassettes/airplay/sending_an_image.yml +26439 -0
  41. data/test/fixtures/cassettes/airplay/stop_any_transmission.yml +8851 -0
  42. data/test/fixtures/files/logo.png +0 -0
  43. data/test/fixtures/files/transition_0.png +0 -0
  44. data/test/fixtures/files/transition_1.png +0 -0
  45. data/test/fixtures/files/transition_2.png +0 -0
  46. data/test/fixtures/files/transition_3.png +0 -0
  47. data/test/integration/discovery_test.rb +13 -0
  48. data/test/integration/features_test.rb +14 -0
  49. data/test/integration/send_media_test.rb +37 -0
  50. data/test/integration/slideshow_test.rb +26 -0
  51. data/test/test_helper.rb +42 -0
  52. data/test/unit/node_test.rb +17 -0
  53. data/test/unit/protocol_test.rb +44 -0
  54. metadata +247 -77
  55. data/.gitignore +0 -2
  56. data/.travis.yml +0 -6
  57. data/examples/jobs.jpg +0 -0
  58. data/examples/send_image.rb +0 -5
  59. data/examples/send_video.rb +0 -7
  60. data/lib/airplay/client.rb +0 -60
  61. data/lib/airplay/protocol/image.rb +0 -41
  62. data/lib/airplay/protocol/media.rb +0 -64
  63. data/lib/airplay/protocol/scrub.rb +0 -37
  64. data/lib/airplay/server.rb +0 -2
  65. data/lib/airplay/server/browser.rb +0 -35
  66. data/lib/airplay/server/node.rb +0 -7
  67. data/test/authentication.rb +0 -13
  68. data/test/discovery.rb +0 -27
  69. data/test/fixtures/cassettes/airplay/authenticate_all_the_things_.yml +0 -139
  70. data/test/fixtures/cassettes/airplay/control_a_video_being_played_in_apple_tv.yml +0 -242
  71. data/test/fixtures/cassettes/airplay/get_current_scrub_from_apple_tv.yml +0 -69
  72. data/test/fixtures/cassettes/airplay/go_to_a_given_position_in_the_video.yml +0 -157
  73. data/test/fixtures/cassettes/airplay/send_audio_to_apple_tv.yml +0 -33
  74. data/test/fixtures/cassettes/airplay/send_image_to_apple_tv.yml +0 -661
  75. data/test/fixtures/cassettes/airplay/send_image_to_apple_tv_with_effects.yml +0 -591
  76. data/test/fixtures/cassettes/airplay/send_video_to_apple_tv.yml +0 -33
  77. data/test/fixtures/image.gif +0 -0
  78. data/test/fixtures/image2.gif +0 -0
  79. data/test/helper.rb +0 -31
  80. data/test/images.rb +0 -31
  81. data/test/media.rb +0 -47
  82. data/test/scrub.rb +0 -31
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- /pkg
2
- Gemfile.lock
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- before_install: sudo apt-get install -y libavahi-compat-libdnssd-dev
2
- rvm:
3
- - 1.8.7
4
- - 1.9.2
5
- - 1.9.3
6
- - jruby
data/examples/jobs.jpg DELETED
Binary file
@@ -1,5 +0,0 @@
1
- require 'airplay'
2
-
3
- client = Airplay::Client.new
4
- client.send_image(File.join(File.dirname(__FILE__), "jobs.jpg"))
5
- sleep(10)
@@ -1,7 +0,0 @@
1
- require 'airplay'
2
- require 'ruby-debug'
3
-
4
- client = Airplay::Client.new
5
- player = client.send_video("http://www.yo-yo.org/mp4/yu.mp4")
6
- # using player you can play with the video
7
- debugger;1
@@ -1,60 +0,0 @@
1
- class Airplay::Client
2
- attr_reader :servers, :active, :password
3
-
4
- def initialize(server = false, server_browser = Airplay::Server::Browser)
5
- @server_browser = server_browser
6
- browse unless server
7
- use servers.first if !@servers.nil?
8
- end
9
-
10
- def use(server)
11
- @active = if server.is_a?(Airplay::Server::Node)
12
- server
13
- else
14
- @server_browser.find_by_name(server)
15
- end
16
- end
17
-
18
- def password(password)
19
- @password = password
20
- end
21
-
22
- def find_by_name(name)
23
- @server_browser.find_by_name(name)
24
- end
25
-
26
- def browse
27
- @servers = @server_browser.browse
28
- end
29
-
30
- def handler
31
- @_handler ||= Airplay::Protocol.new(@active.ip, @active.port, @password)
32
- end
33
-
34
- def send_image(image, transition = :none)
35
- @image_proxy ||= Airplay::Protocol::Image.new(handler)
36
- @image_proxy.send(image, transition)
37
- end
38
-
39
- def send_video(video, position = 0)
40
- @media_proxy ||= Airplay::Protocol::Media.new(handler)
41
- @media_proxy.send(video, position)
42
- end
43
-
44
- def send_audio(audio, position = 0)
45
- @media_proxy ||= Airplay::Protocol::Media.new(handler)
46
- @media_proxy.send(audio, position)
47
- end
48
-
49
- def scrub(position = false)
50
- @scrub_proxy ||= Airplay::Protocol::Scrub.new(handler)
51
- if position
52
- @scrub_proxy.to position
53
- else
54
- @scrub_proxy.check
55
- end
56
- end
57
-
58
- end
59
-
60
- class Airplay::Client::ServerNotFoundError < StandardError; end;
@@ -1,41 +0,0 @@
1
- class Airplay::Protocol::Image
2
-
3
- def initialize(protocol_handler)
4
- @http = protocol_handler
5
- end
6
-
7
- def resource
8
- "/photo"
9
- end
10
-
11
- def transitions
12
- {
13
- :none => "None",
14
- :slide_left => "SlideLeft",
15
- :slide_right => "SlideRight",
16
- :dissolve => "Dissolve"
17
- }
18
- end
19
-
20
- def transition_header(transition)
21
- {"X-Apple-Transition" => transitions.fetch(transition, :none)}
22
- end
23
-
24
- def send(image, transition = :none)
25
- image = URI.parse(image) if !!(image =~ URI::regexp)
26
- content = case image
27
- when String
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
33
- else
34
- throw Airplay::Protocol::InvalidMediaError
35
- end
36
- end
37
-
38
- @http.put(resource, content, transition_header(transition))
39
- end
40
-
41
- end
@@ -1,64 +0,0 @@
1
- class Airplay::Protocol::Media
2
-
3
- def initialize(protocol_handler)
4
- @http = protocol_handler
5
- @scrubber = Airplay::Protocol::Scrub.new(protocol_handler)
6
- end
7
-
8
- def resource
9
- "/play"
10
- end
11
-
12
- def stop_resource
13
- "/stop"
14
- end
15
-
16
- def pause_resource
17
- "/rate"
18
- end
19
-
20
- def position_body(position = 0)
21
- "Start-Position: #{position}\n"
22
- end
23
-
24
- def location_body(media)
25
- "Content-Location: #{media}\n"
26
- end
27
-
28
- def send(media, position = 0)
29
- play(media, position)
30
- self
31
- end
32
-
33
- def play(media, position = 0)
34
- raise Airplay::Protocol::InvalidMediaError if (media =~ URI::regexp).nil?
35
- body = location_body(media)
36
- body += position_body(position.to_s)
37
- @http.post(resource, body)
38
- end
39
-
40
- def pause
41
- rate(0)
42
- end
43
-
44
- def scrub(position = false)
45
- if position
46
- @scrubber.to position
47
- else
48
- @scrubber.check
49
- end
50
- end
51
-
52
- def resume
53
- rate(1)
54
- end
55
-
56
- def rate(play = 1)
57
- @http.post("#{pause_resource}?value=#{play}")
58
- end
59
-
60
- def stop
61
- @http.post(stop_resource)
62
- end
63
-
64
- end
@@ -1,37 +0,0 @@
1
- class Airplay::Protocol::Scrub
2
-
3
- def initialize(protocol_handler)
4
- @http = protocol_handler
5
- end
6
-
7
- def resource
8
- "/scrub"
9
- end
10
-
11
- def plist_headers
12
- {'Content-Type' => 'text/x-apple-plist+xml'}
13
- end
14
-
15
- def check
16
- response = @http.get(resource, plist_headers)
17
- result = {}
18
- response.delete(":").split.each_slice(2) do |key, value|
19
- result[key] = value.to_i
20
- end
21
- result
22
- end
23
-
24
- def to(position)
25
- if position.to_s.include?("%")
26
- percent = position.to_i
27
- raise Airplay::Protocol::OutOfRangeError if percent > 100
28
- duration = check.fetch("duration")
29
- position = (duration*percent)/100
30
- end
31
-
32
- @http.post("#{resource}?position=#{position}\n")
33
- end
34
-
35
- end
36
-
37
- class Airplay::Protocol::OutOfRangeError < StandardError; end;
@@ -1,2 +0,0 @@
1
- class Airplay::Server
2
- end
@@ -1,35 +0,0 @@
1
- module Airplay::Server::Browser
2
- attr_reader :servers
3
-
4
- def self.find_by_name(name)
5
- found_server = @servers.detect do |server|
6
- server if server.name == name
7
- end
8
- raise Airplay::Client::ServerNotFoundError unless found_server
9
- found_server
10
- end
11
-
12
- def self.browse
13
- @servers = []
14
- timeout 3 do
15
- DNSSD.browse!(Airplay::Protocol::SEARCH) do |node|
16
- resolver = DNSSD::Service.new
17
- target, port = nil
18
- resolver.resolve(node) do |resolved|
19
- port = resolved.port
20
- target = resolved.target
21
- break unless resolved.flags.more_coming?
22
- end
23
- info = Socket.getaddrinfo(target, nil, Socket::AF_INET)
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
- end
28
- end
29
- rescue Timeout::Error
30
- raise Airplay::Client::ServerNotFoundError
31
- else
32
- @servers
33
- end
34
-
35
- end
@@ -1,7 +0,0 @@
1
- class Airplay::Server::Node
2
- attr_reader :name, :domain, :ip, :port
3
-
4
- def initialize(name, domain, ip, port)
5
- @name, @domain, @ip, @port = name, domain, ip, port
6
- end
7
- end
@@ -1,13 +0,0 @@
1
- require File.expand_path("helper", File.dirname(__FILE__))
2
-
3
- scope do
4
- test "connect to an authenticated source" do
5
- with_cassette("authenticate all the things!") do
6
- airplay = Airplay::Client.new(false, MockedBrowser)
7
- airplay.password("password")
8
-
9
- assert airplay.scrub.has_key?("duration")
10
- assert airplay.scrub.has_key?("position")
11
- end
12
- end
13
- end
data/test/discovery.rb DELETED
@@ -1,27 +0,0 @@
1
- require File.expand_path("helper", File.dirname(__FILE__))
2
-
3
- scope do
4
-
5
- setup do
6
- @airplay = Airplay::Client.new(false, MockedBrowser)
7
- end
8
-
9
- test "browse for available airplay servers" do
10
- assert @airplay.servers.size > 0
11
- end
12
-
13
- test "find servers by name" do
14
- assert @airplay.find_by_name("Mock TV").is_a?(Airplay::Server::Node)
15
- end
16
-
17
- test "raise on not found" do
18
- assert_raise Airplay::Client::ServerNotFoundError do
19
- @airplay.find_by_name("NotARealAirplayServer")
20
- end
21
- end
22
-
23
- test "autoselect if only one server available" do
24
- assert_equal "Mock TV", @airplay.active.name
25
- end
26
-
27
- end
@@ -1,139 +0,0 @@
1
- ---
2
- http_interactions:
3
- - request:
4
- method: get
5
- uri: http://mocktv.local:7000/scrub
6
- body:
7
- encoding: US-ASCII
8
- base64_string: ""
9
- headers:
10
- user-agent:
11
- - MediaControl/1.0
12
- content-type:
13
- - text/x-apple-plist+xml
14
- connection:
15
- - keep-alive
16
- keep-alive:
17
- - 30
18
- response:
19
- status:
20
- code: 401
21
- message: Unauthorized
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
36
- uri: http://mocktv.local:7000/scrub
37
- body:
38
- encoding: US-ASCII
39
- base64_string: ""
40
- headers:
41
- user-agent:
42
- - MediaControl/1.0
43
- content-type:
44
- - text/x-apple-plist+xml
45
- connection:
46
- - keep-alive
47
- keep-alive:
48
- - 30
49
- host:
50
- - mocktv.local:7000
51
- authorization:
52
- - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMzMjU0MDYzMCArTzzIA84dWO8ijTtj5Rhw", nc=00000000, cnonce="0f55c9413ac973d5963a73572df0805b", response="e6458b63b53f22522a23138c94a5b02b"
53
- response:
54
- status:
55
- code: 200
56
- message: OK
57
- headers:
58
- date:
59
- - Fri, 23 Mar 2012 22:10:35 GMT
60
- content-type:
61
- - text/parameters
62
- content-length:
63
- - "38"
64
- body:
65
- encoding: US-ASCII
66
- base64_string: |
67
- ZHVyYXRpb246IDAuMDAwMDAwCnBvc2l0aW9uOiAwLjAwMDAwMAo=
68
-
69
- http_version: "1.1"
70
- recorded_at: Fri, 23 Mar 2012 22:10:35 GMT
71
- - request:
72
- method: get
73
- uri: http://mocktv.local:7000/scrub
74
- body:
75
- encoding: US-ASCII
76
- base64_string: ""
77
- headers:
78
- user-agent:
79
- - MediaControl/1.0
80
- content-type:
81
- - text/x-apple-plist+xml
82
- connection:
83
- - keep-alive
84
- keep-alive:
85
- - 30
86
- response:
87
- status:
88
- code: 401
89
- message: Unauthorized
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
104
- uri: http://mocktv.local:7000/scrub
105
- body:
106
- encoding: US-ASCII
107
- base64_string: ""
108
- headers:
109
- user-agent:
110
- - MediaControl/1.0
111
- content-type:
112
- - text/x-apple-plist+xml
113
- connection:
114
- - keep-alive
115
- keep-alive:
116
- - 30
117
- host:
118
- - mocktv.local:7000
119
- authorization:
120
- - Digest username="Airplay", realm="AirPlay", uri="/scrub", nonce="MTMzMjU0MDYzMCArTzzIA84dWO8ijTtj5Rhw", nc=00000000, cnonce="0f55c9413ac973d5963a73572df0805b", response="e6458b63b53f22522a23138c94a5b02b"
121
- response:
122
- status:
123
- code: 200
124
- message: OK
125
- headers:
126
- date:
127
- - Fri, 23 Mar 2012 22:10:45 GMT
128
- content-type:
129
- - text/parameters
130
- content-length:
131
- - "38"
132
- body:
133
- encoding: US-ASCII
134
- base64_string: |
135
- ZHVyYXRpb246IDAuMDAwMDAwCnBvc2l0aW9uOiAwLjAwMDAwMAo=
136
-
137
- http_version: "1.1"
138
- recorded_at: Fri, 23 Mar 2012 22:10:45 GMT
139
- recorded_with: VCR 2.0.0