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.
- data/.gitignore +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -7
- data/README.md +2 -1
- data/Rakefile +74 -0
- data/airplay-cli.gemspec +19 -0
- data/airplay.gemspec +5 -8
- data/examples/demo.rb +5 -8
- data/lib/airplay.rb +20 -2
- data/lib/airplay/cli/version.rb +5 -0
- data/lib/airplay/configuration.rb +8 -3
- data/lib/airplay/connection.rb +0 -7
- data/lib/airplay/devices.rb +4 -1
- data/lib/airplay/group.rb +33 -0
- data/lib/airplay/group/players.rb +31 -0
- data/lib/airplay/playable.rb +7 -3
- data/lib/airplay/{protocol/player.rb → player.rb} +37 -17
- data/lib/airplay/player/media.rb +22 -0
- data/lib/airplay/player/playback_info.rb +51 -0
- data/lib/airplay/player/playlist.rb +41 -0
- data/lib/airplay/player/timers.rb +27 -0
- data/lib/airplay/server.rb +56 -0
- data/lib/airplay/server/app.rb +17 -0
- data/lib/airplay/version.rb +3 -0
- data/lib/airplay/viewable.rb +2 -2
- data/lib/airplay/{protocol/viewer.rb → viewer.rb} +2 -2
- metadata +22 -73
- data/lib/airplay/protocol.rb +0 -12
- data/lib/airplay/protocol/app.rb +0 -52
- data/lib/airplay/protocol/message.rb +0 -8
- data/lib/airplay/protocol/playback_info.rb +0 -49
- data/lib/airplay/protocol/reverse.rb +0 -69
- data/lib/airplay/protocol/slideshow.rb +0 -66
- data/lib/airplay/protocol/timers.rb +0 -25
- data/test/fixtures/cassettes/airplay/listing_slideshow_features.yml +0 -201
- data/test/fixtures/cassettes/airplay/play_an_entire_video.yml +0 -100
- data/test/fixtures/cassettes/airplay/sending_a_video.yml +0 -71
- data/test/fixtures/cassettes/airplay/sending_an_image.yml +0 -26439
- data/test/fixtures/cassettes/airplay/stop_any_transmission.yml +0 -8851
- data/test/integration/discovery_test.rb +0 -13
- data/test/integration/features_test.rb +0 -14
- data/test/integration/send_media_test.rb +0 -37
- data/test/integration/slideshow_test.rb +0 -26
- data/test/test_helper.rb +0 -42
- data/test/unit/node_test.rb +0 -17
- data/test/unit/protocol_test.rb +0 -44
@@ -5,10 +5,13 @@ require "celluloid"
|
|
5
5
|
require "cfpropertylist"
|
6
6
|
|
7
7
|
require "airplay/connection"
|
8
|
-
require "airplay/
|
9
|
-
require "airplay/
|
8
|
+
require "airplay/server"
|
9
|
+
require "airplay/player/timers"
|
10
|
+
require "airplay/player/media"
|
11
|
+
require "airplay/player/playback_info"
|
12
|
+
require "airplay/player/playlist"
|
10
13
|
|
11
|
-
module Airplay
|
14
|
+
module Airplay
|
12
15
|
# Public: The class that handles all the video playback
|
13
16
|
#
|
14
17
|
class Player
|
@@ -17,10 +20,16 @@ module Airplay::Protocol
|
|
17
20
|
|
18
21
|
def_delegators :@machine, :state, :on
|
19
22
|
|
23
|
+
attr_reader :device
|
24
|
+
|
20
25
|
def initialize(device)
|
21
26
|
@device = device
|
22
27
|
end
|
23
28
|
|
29
|
+
def playlist
|
30
|
+
@_playlist ||= Playlist.new
|
31
|
+
end
|
32
|
+
|
24
33
|
# Public: Plays a given url or file.
|
25
34
|
# Creates a new persistent connection to ensure that
|
26
35
|
# the socket will be kept alive
|
@@ -28,20 +37,19 @@ module Airplay::Protocol
|
|
28
37
|
# file_or_url - The url or file to be reproduced
|
29
38
|
# options - Optional starting time
|
30
39
|
#
|
31
|
-
def play(
|
40
|
+
def play(media_to_play = "playlist", options = {})
|
32
41
|
start_the_machine
|
33
42
|
check_for_playback_status
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
44
|
+
media = case true
|
45
|
+
when media_to_play.is_a?(Media) then media_to_play
|
46
|
+
when media_to_play == "playlist" && playlist.any?
|
47
|
+
playlist.next
|
48
|
+
else Media.new(media_to_play)
|
49
|
+
end
|
42
50
|
|
43
51
|
content = {
|
44
|
-
"Content-Location" =>
|
52
|
+
"Content-Location" => media,
|
45
53
|
"Start-Position" => options.fetch(:time, 0.0)
|
46
54
|
}
|
47
55
|
|
@@ -65,6 +73,18 @@ module Airplay::Protocol
|
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
76
|
+
def next
|
77
|
+
video = playlist.next
|
78
|
+
play(video) if video
|
79
|
+
video
|
80
|
+
end
|
81
|
+
|
82
|
+
def previous
|
83
|
+
video = playlist.previous
|
84
|
+
play(video) if video
|
85
|
+
video
|
86
|
+
end
|
87
|
+
|
68
88
|
# Public: Shows the current playback time if a video is being played.
|
69
89
|
#
|
70
90
|
# Returns a hash with the :duration and current :position
|
@@ -117,6 +137,11 @@ module Airplay::Protocol
|
|
117
137
|
cleanup
|
118
138
|
end
|
119
139
|
|
140
|
+
def cleanup
|
141
|
+
timers.cancel
|
142
|
+
persistent.close
|
143
|
+
end
|
144
|
+
|
120
145
|
private
|
121
146
|
|
122
147
|
def timers
|
@@ -131,11 +156,6 @@ module Airplay::Protocol
|
|
131
156
|
@_persistent ||= Airplay::Connection.new(@device, keep_alive: true)
|
132
157
|
end
|
133
158
|
|
134
|
-
def cleanup
|
135
|
-
timers.cancel
|
136
|
-
persistent.close
|
137
|
-
end
|
138
|
-
|
139
159
|
def check_for_playback_status
|
140
160
|
timers << every(1) do
|
141
161
|
case true
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Airplay
|
2
|
+
class Player
|
3
|
+
class Media
|
4
|
+
attr_reader :url
|
5
|
+
|
6
|
+
def initialize(file_or_url)
|
7
|
+
@url = case true
|
8
|
+
when File.exists?(file_or_url)
|
9
|
+
Airplay.server.serve(File.expand_path(file_or_url))
|
10
|
+
when !!(file_or_url =~ URI::regexp)
|
11
|
+
file_or_url
|
12
|
+
else
|
13
|
+
raise Errno::ENOENT, file_or_url
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Airplay
|
2
|
+
class Player
|
3
|
+
PlaybackInfo = Struct.new(:info) do
|
4
|
+
def uuid
|
5
|
+
info["uuid"]
|
6
|
+
end
|
7
|
+
|
8
|
+
def duration
|
9
|
+
info["duration"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def position
|
13
|
+
info["position"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def percent
|
17
|
+
return unless position && duration
|
18
|
+
(position * 100 / duration).floor
|
19
|
+
end
|
20
|
+
|
21
|
+
def likely_to_keep_up?
|
22
|
+
info["playbackLikelyToKeepUp"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def stall_count
|
26
|
+
info["stallCount"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def ready_to_play?
|
30
|
+
info["readyToPlay"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def stopped?
|
34
|
+
info.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def playing?
|
38
|
+
info.has_key?("rate") && info.fetch("rate", false) && !info["rate"].zero?
|
39
|
+
end
|
40
|
+
|
41
|
+
def paused?
|
42
|
+
!playing?
|
43
|
+
end
|
44
|
+
|
45
|
+
def played?
|
46
|
+
# This is weird. I know. Bear with me.
|
47
|
+
info.keys.size == 2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "airplay/player/media"
|
3
|
+
|
4
|
+
module Airplay
|
5
|
+
class Player
|
6
|
+
class Playlist
|
7
|
+
include Enumerable
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@items, :each, :size, :empty?
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@items = []
|
14
|
+
@position = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_ary
|
18
|
+
@items
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(file_or_url)
|
22
|
+
@items << Media.new(file_or_url)
|
23
|
+
end
|
24
|
+
|
25
|
+
def next
|
26
|
+
return nil if @position + 1 > @items.size
|
27
|
+
|
28
|
+
item = @items[@position]
|
29
|
+
@position += 1
|
30
|
+
item
|
31
|
+
end
|
32
|
+
|
33
|
+
def previous
|
34
|
+
return nil if @position - 1 < 0
|
35
|
+
|
36
|
+
@position -= 1
|
37
|
+
@items[@position]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Airplay
|
2
|
+
class Player
|
3
|
+
class Timers
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@timers, :each, :size, :empty?
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@timers = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(timer)
|
14
|
+
@timers << timer
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
18
|
+
@timers.each { |t| t.reset }
|
19
|
+
@timers = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def cancel
|
23
|
+
@timers.each { |t| t.cancel }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "socket"
|
3
|
+
require "uuid"
|
4
|
+
|
5
|
+
require "airplay/logger"
|
6
|
+
require "airplay/server/app"
|
7
|
+
|
8
|
+
module Airplay
|
9
|
+
class Server
|
10
|
+
include Celluloid
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@port = Airplay.configuration.port
|
14
|
+
@logger = Airplay::Logger.new("airplay::server")
|
15
|
+
@server = Rack::Server.new(
|
16
|
+
server: :webrick,
|
17
|
+
Host: private_ip,
|
18
|
+
Port: @port,
|
19
|
+
Logger: @logger,
|
20
|
+
AccessLog: [],
|
21
|
+
app: App.app
|
22
|
+
)
|
23
|
+
|
24
|
+
start!
|
25
|
+
end
|
26
|
+
|
27
|
+
def serve(file)
|
28
|
+
sleep 0.1 until running?
|
29
|
+
asset_id = App.settings[:assets][file]
|
30
|
+
|
31
|
+
"http://#{private_ip}:#{@port}/assets/#{asset_id}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def start!
|
35
|
+
Thread.start { @server.start }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def running?
|
41
|
+
begin
|
42
|
+
socket = TCPSocket.new(private_ip, @port)
|
43
|
+
socket.close unless socket.nil?
|
44
|
+
true
|
45
|
+
rescue Errno::ECONNREFUSED, Errno::EBADF, Errno::EADDRNOTAVAIL
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def private_ip
|
51
|
+
@_ip ||= Socket.ip_address_list.detect do |addr|
|
52
|
+
addr.ipv4_private?
|
53
|
+
end.ip_address
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "cuba"
|
2
|
+
|
3
|
+
module Airplay
|
4
|
+
class Server
|
5
|
+
class App < Cuba
|
6
|
+
settings[:assets] ||= Hash.new do |h, k|
|
7
|
+
h[k] = UUID.generate
|
8
|
+
end
|
9
|
+
|
10
|
+
define do
|
11
|
+
on "assets/:uuid" do |uuid|
|
12
|
+
run Rack::File.new(settings[:assets].key(uuid))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/airplay/viewable.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "forwardable"
|
2
|
-
require "airplay/
|
2
|
+
require "airplay/viewer"
|
3
3
|
|
4
4
|
module Airplay
|
5
5
|
module Viewable
|
@@ -10,7 +10,7 @@ module Airplay
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def viewer
|
13
|
-
@_viewer ||= Airplay::
|
13
|
+
@_viewer ||= Airplay::Viewer.new(self)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "open-uri"
|
2
2
|
|
3
|
-
module Airplay
|
3
|
+
module Airplay
|
4
4
|
# Public: The class to handle image broadcast to a device
|
5
5
|
#
|
6
6
|
class Viewer
|
@@ -8,7 +8,7 @@ module Airplay::Protocol
|
|
8
8
|
|
9
9
|
def initialize(device)
|
10
10
|
@device = device
|
11
|
-
@logger = Airplay::Logger.new("airplay::
|
11
|
+
@logger = Airplay::Logger.new("airplay::viewer")
|
12
12
|
end
|
13
13
|
|
14
14
|
# Public: Broadcasts the content to the device
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airplay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta2
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-10-
|
12
|
+
date: 2013-10-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dnssd
|
@@ -128,17 +128,17 @@ dependencies:
|
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
129
|
none: false
|
130
130
|
requirements:
|
131
|
-
- -
|
131
|
+
- - '='
|
132
132
|
- !ruby/object:Gem::Version
|
133
|
-
version: 0.0.
|
133
|
+
version: 0.0.12
|
134
134
|
type: :runtime
|
135
135
|
prerelease: false
|
136
136
|
version_requirements: !ruby/object:Gem::Requirement
|
137
137
|
none: false
|
138
138
|
requirements:
|
139
|
-
- -
|
139
|
+
- - '='
|
140
140
|
- !ruby/object:Gem::Version
|
141
|
-
version: 0.0.
|
141
|
+
version: 0.0.12
|
142
142
|
- !ruby/object:Gem::Dependency
|
143
143
|
name: net-http-digest_auth
|
144
144
|
requirement: !ruby/object:Gem::Requirement
|
@@ -155,38 +155,6 @@ dependencies:
|
|
155
155
|
- - ~>
|
156
156
|
- !ruby/object:Gem::Version
|
157
157
|
version: 1.2.1
|
158
|
-
- !ruby/object:Gem::Dependency
|
159
|
-
name: clap
|
160
|
-
requirement: !ruby/object:Gem::Requirement
|
161
|
-
none: false
|
162
|
-
requirements:
|
163
|
-
- - ~>
|
164
|
-
- !ruby/object:Gem::Version
|
165
|
-
version: 1.0.0
|
166
|
-
type: :runtime
|
167
|
-
prerelease: false
|
168
|
-
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
|
-
requirements:
|
171
|
-
- - ~>
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: 1.0.0
|
174
|
-
- !ruby/object:Gem::Dependency
|
175
|
-
name: ruby-progressbar
|
176
|
-
requirement: !ruby/object:Gem::Requirement
|
177
|
-
none: false
|
178
|
-
requirements:
|
179
|
-
- - ~>
|
180
|
-
- !ruby/object:Gem::Version
|
181
|
-
version: 1.2.0
|
182
|
-
type: :runtime
|
183
|
-
prerelease: false
|
184
|
-
version_requirements: !ruby/object:Gem::Requirement
|
185
|
-
none: false
|
186
|
-
requirements:
|
187
|
-
- - ~>
|
188
|
-
- !ruby/object:Gem::Version
|
189
|
-
version: 1.2.0
|
190
158
|
- !ruby/object:Gem::Dependency
|
191
159
|
name: minitest
|
192
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -238,11 +206,12 @@ dependencies:
|
|
238
206
|
description: Send image/video to an airplay enabled device
|
239
207
|
email:
|
240
208
|
- yo@brunoaguirre.com
|
241
|
-
executables:
|
242
|
-
- air
|
209
|
+
executables: []
|
243
210
|
extensions: []
|
244
211
|
extra_rdoc_files: []
|
245
212
|
files:
|
213
|
+
- .gitignore
|
214
|
+
- .travis.yml
|
246
215
|
- Gemfile
|
247
216
|
- Gemfile.lock
|
248
217
|
- HUGS
|
@@ -250,6 +219,7 @@ files:
|
|
250
219
|
- README.md
|
251
220
|
- Rakefile
|
252
221
|
- SPEC.md
|
222
|
+
- airplay-cli.gemspec
|
253
223
|
- airplay.gemspec
|
254
224
|
- bin/air
|
255
225
|
- examples/demo.rb
|
@@ -258,6 +228,7 @@ files:
|
|
258
228
|
- lib/airplay.rb
|
259
229
|
- lib/airplay/browser.rb
|
260
230
|
- lib/airplay/cli.rb
|
231
|
+
- lib/airplay/cli/version.rb
|
261
232
|
- lib/airplay/configuration.rb
|
262
233
|
- lib/airplay/connection.rb
|
263
234
|
- lib/airplay/connection/authentication.rb
|
@@ -266,36 +237,26 @@ files:
|
|
266
237
|
- lib/airplay/device/features.rb
|
267
238
|
- lib/airplay/device/info.rb
|
268
239
|
- lib/airplay/devices.rb
|
240
|
+
- lib/airplay/group.rb
|
241
|
+
- lib/airplay/group/players.rb
|
269
242
|
- lib/airplay/logger.rb
|
270
243
|
- lib/airplay/playable.rb
|
271
|
-
- lib/airplay/
|
272
|
-
- lib/airplay/
|
273
|
-
- lib/airplay/
|
274
|
-
- lib/airplay/
|
275
|
-
- lib/airplay/
|
276
|
-
- lib/airplay/
|
277
|
-
- lib/airplay/
|
278
|
-
- lib/airplay/protocol/timers.rb
|
279
|
-
- lib/airplay/protocol/viewer.rb
|
244
|
+
- lib/airplay/player.rb
|
245
|
+
- lib/airplay/player/media.rb
|
246
|
+
- lib/airplay/player/playback_info.rb
|
247
|
+
- lib/airplay/player/playlist.rb
|
248
|
+
- lib/airplay/player/timers.rb
|
249
|
+
- lib/airplay/server.rb
|
250
|
+
- lib/airplay/server/app.rb
|
280
251
|
- lib/airplay/structure.rb
|
252
|
+
- lib/airplay/version.rb
|
281
253
|
- lib/airplay/viewable.rb
|
282
|
-
-
|
283
|
-
- test/fixtures/cassettes/airplay/play_an_entire_video.yml
|
284
|
-
- test/fixtures/cassettes/airplay/sending_a_video.yml
|
285
|
-
- test/fixtures/cassettes/airplay/sending_an_image.yml
|
286
|
-
- test/fixtures/cassettes/airplay/stop_any_transmission.yml
|
254
|
+
- lib/airplay/viewer.rb
|
287
255
|
- test/fixtures/files/logo.png
|
288
256
|
- test/fixtures/files/transition_0.png
|
289
257
|
- test/fixtures/files/transition_1.png
|
290
258
|
- test/fixtures/files/transition_2.png
|
291
259
|
- test/fixtures/files/transition_3.png
|
292
|
-
- test/integration/discovery_test.rb
|
293
|
-
- test/integration/features_test.rb
|
294
|
-
- test/integration/send_media_test.rb
|
295
|
-
- test/integration/slideshow_test.rb
|
296
|
-
- test/test_helper.rb
|
297
|
-
- test/unit/node_test.rb
|
298
|
-
- test/unit/protocol_test.rb
|
299
260
|
homepage: http://github.com/elcuervo/airplay
|
300
261
|
licenses:
|
301
262
|
- MIT
|
@@ -323,20 +284,8 @@ signing_key:
|
|
323
284
|
specification_version: 3
|
324
285
|
summary: Airplay client
|
325
286
|
test_files:
|
326
|
-
- test/fixtures/cassettes/airplay/listing_slideshow_features.yml
|
327
|
-
- test/fixtures/cassettes/airplay/play_an_entire_video.yml
|
328
|
-
- test/fixtures/cassettes/airplay/sending_a_video.yml
|
329
|
-
- test/fixtures/cassettes/airplay/sending_an_image.yml
|
330
|
-
- test/fixtures/cassettes/airplay/stop_any_transmission.yml
|
331
287
|
- test/fixtures/files/logo.png
|
332
288
|
- test/fixtures/files/transition_0.png
|
333
289
|
- test/fixtures/files/transition_1.png
|
334
290
|
- test/fixtures/files/transition_2.png
|
335
291
|
- test/fixtures/files/transition_3.png
|
336
|
-
- test/integration/discovery_test.rb
|
337
|
-
- test/integration/features_test.rb
|
338
|
-
- test/integration/send_media_test.rb
|
339
|
-
- test/integration/slideshow_test.rb
|
340
|
-
- test/test_helper.rb
|
341
|
-
- test/unit/node_test.rb
|
342
|
-
- test/unit/protocol_test.rb
|