airplay 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +3 -7
  5. data/README.md +2 -1
  6. data/Rakefile +74 -0
  7. data/airplay-cli.gemspec +19 -0
  8. data/airplay.gemspec +5 -8
  9. data/examples/demo.rb +5 -8
  10. data/lib/airplay.rb +20 -2
  11. data/lib/airplay/cli/version.rb +5 -0
  12. data/lib/airplay/configuration.rb +8 -3
  13. data/lib/airplay/connection.rb +0 -7
  14. data/lib/airplay/devices.rb +4 -1
  15. data/lib/airplay/group.rb +33 -0
  16. data/lib/airplay/group/players.rb +31 -0
  17. data/lib/airplay/playable.rb +7 -3
  18. data/lib/airplay/{protocol/player.rb → player.rb} +37 -17
  19. data/lib/airplay/player/media.rb +22 -0
  20. data/lib/airplay/player/playback_info.rb +51 -0
  21. data/lib/airplay/player/playlist.rb +41 -0
  22. data/lib/airplay/player/timers.rb +27 -0
  23. data/lib/airplay/server.rb +56 -0
  24. data/lib/airplay/server/app.rb +17 -0
  25. data/lib/airplay/version.rb +3 -0
  26. data/lib/airplay/viewable.rb +2 -2
  27. data/lib/airplay/{protocol/viewer.rb → viewer.rb} +2 -2
  28. metadata +22 -73
  29. data/lib/airplay/protocol.rb +0 -12
  30. data/lib/airplay/protocol/app.rb +0 -52
  31. data/lib/airplay/protocol/message.rb +0 -8
  32. data/lib/airplay/protocol/playback_info.rb +0 -49
  33. data/lib/airplay/protocol/reverse.rb +0 -69
  34. data/lib/airplay/protocol/slideshow.rb +0 -66
  35. data/lib/airplay/protocol/timers.rb +0 -25
  36. data/test/fixtures/cassettes/airplay/listing_slideshow_features.yml +0 -201
  37. data/test/fixtures/cassettes/airplay/play_an_entire_video.yml +0 -100
  38. data/test/fixtures/cassettes/airplay/sending_a_video.yml +0 -71
  39. data/test/fixtures/cassettes/airplay/sending_an_image.yml +0 -26439
  40. data/test/fixtures/cassettes/airplay/stop_any_transmission.yml +0 -8851
  41. data/test/integration/discovery_test.rb +0 -13
  42. data/test/integration/features_test.rb +0 -14
  43. data/test/integration/send_media_test.rb +0 -37
  44. data/test/integration/slideshow_test.rb +0 -26
  45. data/test/test_helper.rb +0 -42
  46. data/test/unit/node_test.rb +0 -17
  47. data/test/unit/protocol_test.rb +0 -44
@@ -1,12 +0,0 @@
1
- module Airplay
2
- # Public: The protocol module
3
- #
4
- module Protocol
5
- end
6
- end
7
-
8
- require "airplay/protocol/message"
9
- require "airplay/protocol/image"
10
- require "airplay/protocol/reverse"
11
- require "airplay/protocol/slideshow"
12
- require "airplay/protocol/player"
@@ -1,52 +0,0 @@
1
- require "cuba"
2
- require "base64"
3
- require "cfpropertylist"
4
-
5
- module Airplay::Protocol
6
- # Public: The app to be mounted on the reverse connection
7
- #
8
- class App < Cuba
9
- class << self
10
- attr_accessor :pipeline
11
- end
12
-
13
- def pipe
14
- self.class.pipeline.mailbox
15
- end
16
-
17
- define do
18
- on post, "event" do
19
- plist = CFPropertyList::List.new(data: req.body.read)
20
- native_plist = CFPropertyList.native_types(plist.value)
21
-
22
- message = Message.create(
23
- type: :event,
24
- content: native_plist
25
- )
26
-
27
- pipe << message
28
- end
29
-
30
- on "slideshows/1/assets/:id" do |id|
31
- image = File.read("test/fixtures/files/logo.png")
32
- image.force_encoding("BINARY")
33
-
34
- data = {
35
- data: CFPropertyList::Blob.new(image),
36
- info: {
37
- id: 1,
38
- key: id
39
- }
40
- }
41
-
42
- res["Content-Type"] = "application/x-apple-binary-plist"
43
-
44
- plist = CFPropertyList::List.new
45
- plist.value = CFPropertyList.guess(data)
46
- binary_plist = plist.to_str
47
-
48
- res.write binary_plist
49
- end
50
- end
51
- end
52
- end
@@ -1,8 +0,0 @@
1
- require "airplay/structure"
2
-
3
- module Airplay::Protocol
4
- # Public: Basic class to represent a message from an event callback
5
- #
6
- class Message < Structure.new(:type, :content)
7
- end
8
- end
@@ -1,49 +0,0 @@
1
- module Airplay::Protocol
2
- PlaybackInfo = Struct.new(:info) do
3
- def uuid
4
- info["uuid"]
5
- end
6
-
7
- def duration
8
- info["duration"]
9
- end
10
-
11
- def position
12
- info["position"]
13
- end
14
-
15
- def percent
16
- return unless position && duration
17
- (position * 100 / duration).floor
18
- end
19
-
20
- def likely_to_keep_up?
21
- info["playbackLikelyToKeepUp"]
22
- end
23
-
24
- def stall_count
25
- info["stallCount"]
26
- end
27
-
28
- def ready_to_play?
29
- info["readyToPlay"]
30
- end
31
-
32
- def stopped?
33
- info.empty?
34
- end
35
-
36
- def playing?
37
- info.has_key?("rate") && info.fetch("rate", false) && !info["rate"].zero?
38
- end
39
-
40
- def paused?
41
- !playing?
42
- end
43
-
44
- def played?
45
- # This is weird. I know. Bear with me.
46
- info.keys.size == 2
47
- end
48
- end
49
- end
@@ -1,69 +0,0 @@
1
- require "celluloid"
2
- require "net/ptth"
3
- require "net/http"
4
- require "airplay/protocol/app"
5
- require "airplay/connection/authentication"
6
-
7
- module Airplay::Protocol
8
- # Public: Handles the reverse connection
9
- #
10
- class Reverse
11
- include Celluloid
12
-
13
- attr_accessor :callbacks
14
- attr_reader :state
15
-
16
- def initialize(device, purpose = "event")
17
- @logger = Airplay::Logger.new("airplay::protocol::reverse")
18
-
19
- @ptth = Net::PTTH.new("http://#{device.address}")
20
- @ptth.set_debug_output = @logger
21
- @state = "disconnected"
22
- @purpose = purpose
23
- @ptth.app = Airplay.app
24
-
25
- @callbacks = []
26
-
27
- async.pipeline
28
- @ptth.app.pipeline = self.async
29
- end
30
-
31
- # Public: Disconnects the current connection
32
- #
33
- def disconnect
34
- @state = "disconnected"
35
- @ptth.close
36
- end
37
-
38
- # Public: Connects to the reverse resource and starts the switching
39
- #
40
- def connect
41
- @logger.info "Connecting with purpose: #{@purpose}"
42
-
43
- request = Net::HTTP::Post.new("/reverse")
44
-
45
- request["X-Apple-Purpose"] = @purpose
46
- request["X-Apple-Device-ID"] = "0x581faa7c9754"
47
-
48
- if Airplay.active.password?
49
- authentication = Airplay::Connection::Authentication.new(@ptth)
50
- request = authentication.sign(request)
51
- end
52
-
53
- @ptth.request(request)
54
- @state = "connected"
55
- end
56
-
57
- # Public: Pipelines all the incomming messages to the callback ppol
58
- #
59
- def pipeline
60
- loop do
61
- message = receive { |msg| msg.is_a? Message }
62
- @logger.debug "Incomming message from the event pipeline: #{message}"
63
- @callbacks.each do |callback|
64
- callback.call(message.content)
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,66 +0,0 @@
1
- module Airplay::Protocol
2
- # Public: Handles the slideshows
3
- #
4
- class Slideshow
5
- def initialize
6
- @images = []
7
- end
8
-
9
- # Public: Lists all the slideshow features for the active device
10
- #
11
- # Returns the available themes hash
12
- #
13
- def features
14
- @_features ||= begin
15
- response = Airplay.connection.get("/slideshow-features", {
16
- "Accept-Language" => "English"
17
- })
18
-
19
- plist = CFPropertyList::List.new(data: response.body)
20
- native_plist = CFPropertyList.native_types(plist.value)
21
-
22
- native_plist["themes"]
23
- end
24
- end
25
-
26
- # Public: Adds images to the slideshow pool
27
- #
28
- # images - An array of images to be added
29
- #
30
- def <<(*images)
31
- images.each { |image| @images << image }
32
- end
33
-
34
- # Public: Gets an images given a position on the list
35
- #
36
- # position - The position on the list
37
- #
38
- def [](position)
39
- @images[position]
40
- end
41
-
42
- # Public: Pays the current slideshow
43
- #
44
- def play
45
- @reverse = Airplay::Protocol::Reverse.new(Airplay.active, "slideshow")
46
- @reverse.async.connect
47
-
48
- content = {
49
- settings: {
50
- slideDuration: 3,
51
- theme: "Dissolve".upcase
52
- },
53
- state: "playing"
54
- }
55
-
56
- plist = CFPropertyList::List.new
57
- plist.value = CFPropertyList.guess(content)
58
- binary = plist.to_str(2)
59
-
60
- response = Airplay.connection.async.put("/slideshows/1", binary, {
61
- "Content-Type" => "text/x-apple-plist+xml"
62
- })
63
-
64
- end
65
- end
66
- end
@@ -1,25 +0,0 @@
1
- module Airplay::Protocol
2
- class Timers
3
- include Enumerable
4
- extend Forwardable
5
-
6
- def_delegators :@timers, :each, :size, :empty?
7
-
8
- def initialize
9
- @timers = []
10
- end
11
-
12
- def <<(timer)
13
- @timers << timer
14
- end
15
-
16
- def reset
17
- @timers.each { |t| t.reset }
18
- @timers = []
19
- end
20
-
21
- def cancel
22
- @timers.each { |t| t.cancel }
23
- end
24
- end
25
- end
@@ -1,201 +0,0 @@
1
- ---
2
- http_interactions:
3
- - request:
4
- method: get
5
- uri: http://192.168.100.205:7000/slideshow-features
6
- body:
7
- encoding: US-ASCII
8
- base64_string: ''
9
- headers:
10
- user-agent:
11
- - MediaControl/1.0
12
- x-apple-session-id:
13
- - bee61160-51f2-0130-f43c-705681a9dd8d
14
- accept-language:
15
- - English
16
- connection:
17
- - keep-alive
18
- keep-alive:
19
- - 30
20
- response:
21
- status:
22
- code: 200
23
- message: OK
24
- headers:
25
- date:
26
- - Tue, 05 Feb 2013 16:49:51 GMT
27
- content-type:
28
- - text/x-apple-plist+xml
29
- content-length:
30
- - '1479'
31
- body:
32
- encoding: US-ASCII
33
- base64_string: ! 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
34
-
35
- WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
36
-
37
- IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
38
-
39
- LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+dGhl
40
-
41
- bWVzPC9rZXk+Cgk8YXJyYXk+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+
42
-
43
- CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9r
44
-
45
- ZXk+CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJ
46
-
47
- PGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+U3dpcGVSaWdo
48
-
49
- dDwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJPHN0cmluZz5Td2lw
50
-
51
- ZSBSaWdodDwvc3RyaW5nPgoJCTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5r
52
-
53
- ZXk8L2tleT4KCQkJPHN0cmluZz5Td2lwZUxlZnQ8L3N0cmluZz4KCQkJPGtl
54
-
55
- eT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+U3dpcGUgTGVmdDwvc3RyaW5nPgoJ
56
-
57
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
58
-
59
- Zz5SaXBwbGU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJp
60
-
61
- bmc+UmlwcGxlPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5
62
-
63
- PmtleTwva2V5PgoJCQk8c3RyaW5nPlBhZ2VDdXJsPC9zdHJpbmc+CgkJCTxr
64
-
65
- ZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPlBhZ2UgQ3VybDwvc3RyaW5nPgoJ
66
-
67
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
68
-
69
- Zz5Db3B5TWFjaGluZTwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJ
70
-
71
- PHN0cmluZz5Db3B5IE1hY2hpbmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRp
72
-
73
- Y3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+QmFyc1N3aXBlPC9z
74
-
75
- dHJpbmc+CgkJCTxrZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPkJhcnMgU3dp
76
-
77
- cGU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9r
78
-
79
- ZXk+CgkJCTxzdHJpbmc+RGlzaW50ZWdyYXRlPC9zdHJpbmc+CgkJCTxrZXk+
80
-
81
- bmFtZTwva2V5PgoJCQk8c3RyaW5nPkRpc2ludGVncmF0ZTwvc3RyaW5nPgoJ
82
-
83
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
84
-
85
- Zz5Nb2Q8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+
86
-
87
- TW9kPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5PmtleTwv
88
-
89
- a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJCTxrZXk+bmFtZTwv
90
-
91
- a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxk
92
-
93
- aWN0PgoJCQk8a2V5PmtleTwva2V5PgoJCQk8c3RyaW5nPk5vbmU8L3N0cmlu
94
-
95
- Zz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+Tm9uZTwvc3RyaW5n
96
-
97
- PgoJCTwvZGljdD4KCTwvYXJyYXk+CjwvZGljdD4KPC9wbGlzdD4K
98
-
99
- '
100
- http_version: '1.1'
101
- recorded_at: Tue, 05 Feb 2013 18:49:51 GMT
102
- - request:
103
- method: get
104
- uri: http://192.168.100.179:7000/slideshow-features
105
- body:
106
- encoding: US-ASCII
107
- base64_string: ''
108
- headers:
109
- user-agent:
110
- - MediaControl/1.0
111
- x-apple-session-id:
112
- - 74bc36c0-5213-0130-f46a-705681a9dd8d
113
- accept-language:
114
- - English
115
- connection:
116
- - keep-alive
117
- keep-alive:
118
- - 30
119
- response:
120
- status:
121
- code: 200
122
- message: OK
123
- headers:
124
- date:
125
- - Tue, 05 Feb 2013 20:44:00 GMT
126
- content-type:
127
- - text/x-apple-plist+xml
128
- content-length:
129
- - '1479'
130
- body:
131
- encoding: US-ASCII
132
- base64_string: ! 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
133
-
134
- WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
135
-
136
- IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
137
-
138
- LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+dGhl
139
-
140
- bWVzPC9rZXk+Cgk8YXJyYXk+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+
141
-
142
- CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9r
143
-
144
- ZXk+CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJ
145
-
146
- PGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+U3dpcGVSaWdo
147
-
148
- dDwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJPHN0cmluZz5Td2lw
149
-
150
- ZSBSaWdodDwvc3RyaW5nPgoJCTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5r
151
-
152
- ZXk8L2tleT4KCQkJPHN0cmluZz5Td2lwZUxlZnQ8L3N0cmluZz4KCQkJPGtl
153
-
154
- eT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+U3dpcGUgTGVmdDwvc3RyaW5nPgoJ
155
-
156
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
157
-
158
- Zz5SaXBwbGU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJp
159
-
160
- bmc+UmlwcGxlPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5
161
-
162
- PmtleTwva2V5PgoJCQk8c3RyaW5nPlBhZ2VDdXJsPC9zdHJpbmc+CgkJCTxr
163
-
164
- ZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPlBhZ2UgQ3VybDwvc3RyaW5nPgoJ
165
-
166
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
167
-
168
- Zz5Db3B5TWFjaGluZTwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJ
169
-
170
- PHN0cmluZz5Db3B5IE1hY2hpbmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRp
171
-
172
- Y3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+QmFyc1N3aXBlPC9z
173
-
174
- dHJpbmc+CgkJCTxrZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPkJhcnMgU3dp
175
-
176
- cGU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9r
177
-
178
- ZXk+CgkJCTxzdHJpbmc+RGlzaW50ZWdyYXRlPC9zdHJpbmc+CgkJCTxrZXk+
179
-
180
- bmFtZTwva2V5PgoJCQk8c3RyaW5nPkRpc2ludGVncmF0ZTwvc3RyaW5nPgoJ
181
-
182
- CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
183
-
184
- Zz5Nb2Q8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+
185
-
186
- TW9kPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5PmtleTwv
187
-
188
- a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJCTxrZXk+bmFtZTwv
189
-
190
- a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxk
191
-
192
- aWN0PgoJCQk8a2V5PmtleTwva2V5PgoJCQk8c3RyaW5nPk5vbmU8L3N0cmlu
193
-
194
- Zz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+Tm9uZTwvc3RyaW5n
195
-
196
- PgoJCTwvZGljdD4KCTwvYXJyYXk+CjwvZGljdD4KPC9wbGlzdD4K
197
-
198
- '
199
- http_version: '1.1'
200
- recorded_at: Tue, 05 Feb 2013 22:44:00 GMT
201
- recorded_with: VCR 2.4.0