airplay 1.0.0.beta3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +23 -7
- data/Rakefile +1 -1
- data/airplay-cli.gemspec +1 -1
- data/bin/air +26 -16
- data/doc/documentation.md +4 -0
- data/doc/img/cli_list.png +0 -0
- data/doc/img/cli_play.png +0 -0
- data/doc/usage.md +14 -4
- data/lib/airplay.rb +39 -1
- data/lib/airplay/browser.rb +13 -3
- data/lib/airplay/cli.rb +32 -55
- data/lib/airplay/cli/image_viewer.rb +67 -0
- data/lib/airplay/cli/version.rb +2 -2
- data/lib/airplay/configuration.rb +4 -0
- data/lib/airplay/connection.rb +20 -10
- data/lib/airplay/device.rb +48 -0
- data/lib/airplay/device/features.rb +1 -1
- data/lib/airplay/devices.rb +25 -3
- data/lib/airplay/group.rb +20 -0
- data/lib/airplay/logger.rb +3 -1
- data/lib/airplay/playable.rb +13 -3
- data/lib/airplay/player.rb +65 -4
- data/lib/airplay/server.rb +18 -0
- data/lib/airplay/version.rb +1 -1
- data/lib/airplay/viewable.rb +4 -0
- data/lib/airplay/viewer.rb +17 -1
- metadata +9 -9
- data/SPEC.md +0 -34
- data/examples/demo.rb +0 -32
- data/examples/image.rb +0 -9
- data/examples/video.rb +0 -24
@@ -0,0 +1,67 @@
|
|
1
|
+
module Airplay
|
2
|
+
module CLI
|
3
|
+
class ImageViewer
|
4
|
+
attr_reader :options, :devices
|
5
|
+
|
6
|
+
def initialize(devices, options = {})
|
7
|
+
@devices = Array(devices)
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def view(file, transition = "SlideLeft")
|
12
|
+
puts "Showing #{file}"
|
13
|
+
devices.each do |device|
|
14
|
+
device.view(file, transition: transition)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def slideshow(files)
|
19
|
+
puts "Autoplay every #{options[:wait]}"
|
20
|
+
files.each do |file|
|
21
|
+
view(file)
|
22
|
+
sleep options[:wait]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def interactive(files)
|
27
|
+
numbers = Array(0...files.count)
|
28
|
+
transition = "None"
|
29
|
+
|
30
|
+
i = 0
|
31
|
+
loop do
|
32
|
+
view(files[i], transition)
|
33
|
+
|
34
|
+
case read_char
|
35
|
+
when "\e[C" # Right Arrow
|
36
|
+
i = i + 1 > numbers.count - 1 ? 0 : i + 1
|
37
|
+
transition = "SlideLeft"
|
38
|
+
when "\e[D" # Left Arrow
|
39
|
+
i = i - 1 < 0 ? numbers.count - 1 : i - 1
|
40
|
+
transition = "SlideRight"
|
41
|
+
else
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def read_char
|
50
|
+
STDIN.echo = false
|
51
|
+
STDIN.raw!
|
52
|
+
|
53
|
+
input = STDIN.getc.chr
|
54
|
+
if input == "\e" then
|
55
|
+
input << STDIN.read_nonblock(3) rescue nil
|
56
|
+
input << STDIN.read_nonblock(2) rescue nil
|
57
|
+
end
|
58
|
+
ensure
|
59
|
+
STDIN.echo = true
|
60
|
+
STDIN.cooked!
|
61
|
+
|
62
|
+
return input
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/airplay/cli/version.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "log4r/config"
|
2
2
|
require "airplay/logger"
|
3
3
|
|
4
|
+
# Public: Airplay core module
|
5
|
+
#
|
4
6
|
module Airplay
|
5
7
|
# Public: Handles the Airplay configuration
|
6
8
|
#
|
@@ -19,6 +21,8 @@ module Airplay
|
|
19
21
|
|
20
22
|
# Public: Loads the configuration into the affected parts
|
21
23
|
#
|
24
|
+
# Returns nothing.
|
25
|
+
#
|
22
26
|
def load
|
23
27
|
level = if @log_level.is_a?(Fixnum)
|
24
28
|
@log_level
|
data/lib/airplay/connection.rb
CHANGED
@@ -16,11 +16,19 @@ module Airplay
|
|
16
16
|
@logger = Airplay::Logger.new("airplay::connection")
|
17
17
|
end
|
18
18
|
|
19
|
+
# Public: Establishes a persistent connection to the device
|
20
|
+
#
|
21
|
+
# Returns the persistent connection
|
22
|
+
#
|
19
23
|
def persistent
|
20
24
|
address = @options[:address] || "http://#{@device.address}"
|
21
25
|
@_persistent ||= Airplay::Connection::Persistent.new(address, @options)
|
22
26
|
end
|
23
27
|
|
28
|
+
# Public: Closes the opened connection
|
29
|
+
#
|
30
|
+
# Returns nothing
|
31
|
+
#
|
24
32
|
def close
|
25
33
|
persistent.close
|
26
34
|
@_persistent = nil
|
@@ -28,9 +36,9 @@ module Airplay
|
|
28
36
|
|
29
37
|
# Public: Executes a POST to a resource
|
30
38
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
39
|
+
# resource - The resource on the currently active Device
|
40
|
+
# body - The body of the action
|
41
|
+
# headers - Optional headers
|
34
42
|
#
|
35
43
|
# Returns a response object
|
36
44
|
#
|
@@ -44,9 +52,9 @@ module Airplay
|
|
44
52
|
|
45
53
|
# Public: Executes a PUT to a resource
|
46
54
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
55
|
+
# resource - The resource on the currently active Device
|
56
|
+
# body - The body of the action
|
57
|
+
# headers - Optional headers
|
50
58
|
#
|
51
59
|
# Returns a response object
|
52
60
|
#
|
@@ -60,8 +68,8 @@ module Airplay
|
|
60
68
|
|
61
69
|
# Public: Executes a GET to a resource
|
62
70
|
#
|
63
|
-
#
|
64
|
-
#
|
71
|
+
# resource - The resource on the currently active Device
|
72
|
+
# headers - Optional headers
|
65
73
|
#
|
66
74
|
# Returns a response object
|
67
75
|
#
|
@@ -76,6 +84,8 @@ module Airplay
|
|
76
84
|
|
77
85
|
# Private: The defaults connection headers
|
78
86
|
#
|
87
|
+
# Returns the default headers
|
88
|
+
#
|
79
89
|
def default_headers
|
80
90
|
{
|
81
91
|
"User-Agent" => "MediaControl/1.0",
|
@@ -85,8 +95,8 @@ module Airplay
|
|
85
95
|
|
86
96
|
# Private: Sends a request to the Device
|
87
97
|
#
|
88
|
-
#
|
89
|
-
#
|
98
|
+
# request - The Request object
|
99
|
+
# headers - The headers of the request
|
90
100
|
#
|
91
101
|
# Returns a response object
|
92
102
|
#
|
data/lib/airplay/device.rb
CHANGED
@@ -20,44 +20,88 @@ module Airplay
|
|
20
20
|
Airplay.configuration.load
|
21
21
|
end
|
22
22
|
|
23
|
+
# Public: Access the ip of the device
|
24
|
+
#
|
25
|
+
# Returns the memoized ip address
|
26
|
+
#
|
23
27
|
def ip
|
24
28
|
@_ip ||= address.split(":").first
|
25
29
|
end
|
26
30
|
|
31
|
+
# Public: Sets the password for the device
|
32
|
+
#
|
33
|
+
# passwd - The password string
|
34
|
+
#
|
35
|
+
# Returns nothing
|
36
|
+
#
|
27
37
|
def password=(passwd)
|
28
38
|
@password = passwd
|
29
39
|
end
|
30
40
|
|
41
|
+
# Public: Checks if the devices has a password
|
42
|
+
#
|
43
|
+
# Returns boolean for the presence of a password
|
44
|
+
#
|
31
45
|
def password?
|
32
46
|
!!password && !password.empty?
|
33
47
|
end
|
34
48
|
|
49
|
+
# Public: Set the addess of the device
|
50
|
+
#
|
51
|
+
# address - The address string of the device
|
52
|
+
#
|
53
|
+
# Returns nothing
|
54
|
+
#
|
35
55
|
def address=(address)
|
36
56
|
@address = address
|
37
57
|
end
|
38
58
|
|
59
|
+
# Public: Access the Features of the device
|
60
|
+
#
|
61
|
+
# Returns the Featurs of the device
|
62
|
+
#
|
39
63
|
def features
|
40
64
|
@_features ||= Features.new(self)
|
41
65
|
end
|
42
66
|
|
67
|
+
# Public: Access the Info of the device
|
68
|
+
#
|
69
|
+
# Returns the Info of the device
|
70
|
+
#
|
43
71
|
def info
|
44
72
|
@_info ||= Info.new(self)
|
45
73
|
end
|
46
74
|
|
75
|
+
# Public: Access the full information of the device
|
76
|
+
#
|
77
|
+
# Returns a hash with all the information
|
78
|
+
#
|
47
79
|
def server_info
|
48
80
|
@_server_info ||= basic_info.merge(extra_info)
|
49
81
|
end
|
50
82
|
|
83
|
+
# Public: Establishes a conection to the device
|
84
|
+
#
|
85
|
+
# Returns the Connection
|
86
|
+
#
|
51
87
|
def connection
|
52
88
|
@_connection ||= Airplay::Connection.new(self)
|
53
89
|
end
|
54
90
|
|
91
|
+
# Public: Forces the refresh of the connection
|
92
|
+
#
|
93
|
+
# Returns nothing
|
94
|
+
#
|
55
95
|
def refresh_connection
|
56
96
|
@_connection = nil
|
57
97
|
end
|
58
98
|
|
59
99
|
private
|
60
100
|
|
101
|
+
# Private: Access the basic info of the device
|
102
|
+
#
|
103
|
+
# Returns a hash with the basic information
|
104
|
+
#
|
61
105
|
def basic_info
|
62
106
|
@_basic_info ||= begin
|
63
107
|
response = connection.get("/server-info").response
|
@@ -66,6 +110,10 @@ module Airplay
|
|
66
110
|
end
|
67
111
|
end
|
68
112
|
|
113
|
+
# Private: Access extra info of the device
|
114
|
+
#
|
115
|
+
# Returns a hash with extra information
|
116
|
+
#
|
69
117
|
def extra_info
|
70
118
|
@_extra_info ||= begin
|
71
119
|
new_device = clone
|
@@ -21,7 +21,7 @@ module Airplay
|
|
21
21
|
photo?: 0 < (hex & ( 1 << 1 )),
|
22
22
|
video_fair_play?: 0 < (hex & ( 1 << 2 )),
|
23
23
|
video_volume_control?: 0 < (hex & ( 1 << 3 )),
|
24
|
-
|
24
|
+
video_http_live_stream?: 0 < (hex & ( 1 << 4 )),
|
25
25
|
slideshow?: 0 < (hex & ( 1 << 5 )),
|
26
26
|
screen?: 0 < (hex & ( 1 << 7 )),
|
27
27
|
screen_rotate?: 0 < (hex & ( 1 << 8 )),
|
data/lib/airplay/devices.rb
CHANGED
@@ -16,25 +16,41 @@ module Airplay
|
|
16
16
|
|
17
17
|
# Public: Finds a device given a name
|
18
18
|
#
|
19
|
-
#
|
19
|
+
# device_name - The name of the device
|
20
20
|
#
|
21
|
-
# Returns a
|
21
|
+
# Returns a Device object
|
22
22
|
#
|
23
23
|
def find_by_name(device_name)
|
24
24
|
find_by_block { |device| device if device.name == device_name }
|
25
25
|
end
|
26
26
|
|
27
|
+
# Public: Finds a device given an ip
|
28
|
+
#
|
29
|
+
# ip - The ip of the device
|
30
|
+
#
|
31
|
+
# Returns a Device object
|
32
|
+
#
|
27
33
|
def find_by_ip(ip)
|
28
34
|
find_by_block { |device| device if device.ip == ip }
|
29
35
|
end
|
30
36
|
|
37
|
+
# Public: Adds a device to the pool
|
38
|
+
#
|
39
|
+
# name - The name of the device
|
40
|
+
# address - The address of the device
|
41
|
+
#
|
42
|
+
# Returns nothing
|
43
|
+
#
|
31
44
|
def add(name, address)
|
32
45
|
self << Device.new(name: name, address: address)
|
33
46
|
end
|
34
47
|
|
35
48
|
# Public: Adds a device to the list
|
36
49
|
#
|
37
|
-
#
|
50
|
+
# value - The Device
|
51
|
+
#
|
52
|
+
# Returns nothing
|
53
|
+
#
|
38
54
|
def <<(device)
|
39
55
|
return if find_by_ip(device.ip)
|
40
56
|
@items << device
|
@@ -42,6 +58,12 @@ module Airplay
|
|
42
58
|
|
43
59
|
private
|
44
60
|
|
61
|
+
# Private: Finds a devices based on a block
|
62
|
+
#
|
63
|
+
# &block - The block to be executed
|
64
|
+
#
|
65
|
+
# Returns the result of the find on that given block
|
66
|
+
#
|
45
67
|
def find_by_block(&block)
|
46
68
|
@items.find(&block)
|
47
69
|
end
|
data/lib/airplay/group.rb
CHANGED
@@ -14,15 +14,35 @@ module Airplay
|
|
14
14
|
@name = name
|
15
15
|
end
|
16
16
|
|
17
|
+
# Public: Adds a device to the list
|
18
|
+
#
|
19
|
+
# value - The Device
|
20
|
+
#
|
21
|
+
# Returns nothing
|
22
|
+
#
|
17
23
|
def <<(device)
|
18
24
|
@devices << device
|
19
25
|
end
|
20
26
|
|
27
|
+
# Public: Plays a video on all the grouped devices
|
28
|
+
#
|
29
|
+
# file_or_url - The file or url to be sent to the devices
|
30
|
+
# options - The options to be sent
|
31
|
+
#
|
32
|
+
# Returns a Players instance that syncs the devices
|
33
|
+
#
|
21
34
|
def play(file_or_url, options = {})
|
22
35
|
@players = @devices.map { |device| device.play(file_or_url, options) }
|
23
36
|
Players.new(@players)
|
24
37
|
end
|
25
38
|
|
39
|
+
# Public: Views an image on all the grouped devices
|
40
|
+
#
|
41
|
+
# media_or_io - The file or url to be sent to the devices
|
42
|
+
# options - The options to be sent
|
43
|
+
#
|
44
|
+
# Returns an array of arrays with the result of the playback
|
45
|
+
#
|
26
46
|
def view(media_or_io, options = {})
|
27
47
|
@devices.map do |device|
|
28
48
|
ok = device.view(media_or_io, options)
|
data/lib/airplay/logger.rb
CHANGED
data/lib/airplay/playable.rb
CHANGED
@@ -2,11 +2,10 @@ require "airplay/player"
|
|
2
2
|
|
3
3
|
module Airplay
|
4
4
|
module Playable
|
5
|
-
|
6
5
|
# Public: Plays a given video
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# file_or_url - The video to be played
|
8
|
+
# options - Optional start position
|
10
9
|
#
|
11
10
|
# Returns a Player object to control the playback
|
12
11
|
#
|
@@ -15,14 +14,25 @@ module Airplay
|
|
15
14
|
player
|
16
15
|
end
|
17
16
|
|
17
|
+
# Public: Gets the current playlist
|
18
|
+
#
|
19
|
+
# Returns the Playlist
|
20
|
+
#
|
18
21
|
def playlist
|
19
22
|
player.playlist
|
20
23
|
end
|
21
24
|
|
25
|
+
# Public: Gets all the playlists
|
26
|
+
#
|
27
|
+
# Returns the Playlists
|
28
|
+
#
|
22
29
|
def playlists
|
23
30
|
player.playlists
|
24
31
|
end
|
25
32
|
|
33
|
+
# Public: Gets the player object
|
34
|
+
#
|
35
|
+
# Returns a Player object
|
26
36
|
def player
|
27
37
|
@_player ||= Airplay::Player.new(self)
|
28
38
|
end
|
data/lib/airplay/player.rb
CHANGED
@@ -26,10 +26,18 @@ module Airplay
|
|
26
26
|
@device = device
|
27
27
|
end
|
28
28
|
|
29
|
+
# Public: Gets all the playlists
|
30
|
+
#
|
31
|
+
# Returns the Playlists
|
32
|
+
#
|
29
33
|
def playlists
|
30
34
|
@_playlists ||= Hash.new { |h,k| h[k] = Playlist.new(k) }
|
31
35
|
end
|
32
36
|
|
37
|
+
# Public: Gets the current playlist
|
38
|
+
#
|
39
|
+
# Returns the first Playlist if none defined or creates a new one
|
40
|
+
#
|
33
41
|
def playlist
|
34
42
|
@_playlist ||= if playlists.any?
|
35
43
|
key, value = playlists.first
|
@@ -39,6 +47,12 @@ module Airplay
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
50
|
+
# Public: Sets a given playlist
|
51
|
+
#
|
52
|
+
# name - The name of the playlist to be used
|
53
|
+
#
|
54
|
+
# Returns nothing
|
55
|
+
#
|
42
56
|
def use(name)
|
43
57
|
@_playlist = playlists[name]
|
44
58
|
end
|
@@ -47,8 +61,10 @@ module Airplay
|
|
47
61
|
# Creates a new persistent connection to ensure that
|
48
62
|
# the socket will be kept alive
|
49
63
|
#
|
50
|
-
#
|
51
|
-
#
|
64
|
+
# file_or_url - The url or file to be reproduced
|
65
|
+
# options - Optional starting time
|
66
|
+
#
|
67
|
+
# Returns nothing
|
52
68
|
#
|
53
69
|
def play(media_to_play = "playlist", options = {})
|
54
70
|
start_the_machine
|
@@ -78,7 +94,9 @@ module Airplay
|
|
78
94
|
# Public: Handles the progress of the playback, the given &block get's
|
79
95
|
# executed every second while the video is played.
|
80
96
|
#
|
81
|
-
#
|
97
|
+
# &block - Block to be executed in every playable second.
|
98
|
+
#
|
99
|
+
# Returns nothing
|
82
100
|
#
|
83
101
|
def progress(callback)
|
84
102
|
timers << every(1) do
|
@@ -86,12 +104,20 @@ module Airplay
|
|
86
104
|
end
|
87
105
|
end
|
88
106
|
|
107
|
+
# Public: Plays the next video in the playlist
|
108
|
+
#
|
109
|
+
# Returns the video that was selected or nil if none
|
110
|
+
#
|
89
111
|
def next
|
90
112
|
video = playlist.next
|
91
113
|
play(video) if video
|
92
114
|
video
|
93
115
|
end
|
94
116
|
|
117
|
+
# Public: Plays the previous video in the playlist
|
118
|
+
#
|
119
|
+
# Returns the video that was selected or nil if none
|
120
|
+
#
|
95
121
|
def previous
|
96
122
|
video = playlist.previous
|
97
123
|
play(video) if video
|
@@ -111,7 +137,7 @@ module Airplay
|
|
111
137
|
|
112
138
|
# Public: checks current playback information
|
113
139
|
#
|
114
|
-
# Returns a
|
140
|
+
# Returns a PlaybackInfo object with the playback information
|
115
141
|
#
|
116
142
|
def info
|
117
143
|
response = connection.get("/playback-info").response
|
@@ -122,18 +148,24 @@ module Airplay
|
|
122
148
|
|
123
149
|
# Public: Resumes a paused video
|
124
150
|
#
|
151
|
+
# Returns nothing
|
152
|
+
#
|
125
153
|
def resume
|
126
154
|
connection.async.post("/rate?value=1")
|
127
155
|
end
|
128
156
|
|
129
157
|
# Public: Pauses a playing video
|
130
158
|
#
|
159
|
+
# Returns nothing
|
160
|
+
#
|
131
161
|
def pause
|
132
162
|
connection.async.post("/rate?value=0")
|
133
163
|
end
|
134
164
|
|
135
165
|
# Public: Stops the video
|
136
166
|
#
|
167
|
+
# Returns nothing
|
168
|
+
#
|
137
169
|
def stop
|
138
170
|
connection.post("/stop")
|
139
171
|
end
|
@@ -146,11 +178,17 @@ module Airplay
|
|
146
178
|
|
147
179
|
# Public: Locks the execution until the video gets fully played
|
148
180
|
#
|
181
|
+
# Returns nothing
|
182
|
+
#
|
149
183
|
def wait
|
150
184
|
sleep 1 while wait_for_playback?
|
151
185
|
cleanup
|
152
186
|
end
|
153
187
|
|
188
|
+
# Public: Cleans up the player
|
189
|
+
#
|
190
|
+
# Returns nothing
|
191
|
+
#
|
154
192
|
def cleanup
|
155
193
|
timers.cancel
|
156
194
|
persistent.close
|
@@ -158,23 +196,44 @@ module Airplay
|
|
158
196
|
|
159
197
|
private
|
160
198
|
|
199
|
+
# Private: Returns if we have to wait for playback
|
200
|
+
#
|
201
|
+
# Returns a boolean if we need to wait
|
202
|
+
#
|
161
203
|
def wait_for_playback?
|
162
204
|
return true if playlist.next?
|
163
205
|
loading? || playing? || paused?
|
164
206
|
end
|
165
207
|
|
208
|
+
# Private: The timers
|
209
|
+
#
|
210
|
+
# Returns a Timers object
|
211
|
+
#
|
166
212
|
def timers
|
167
213
|
@_timers ||= Timers.new
|
168
214
|
end
|
169
215
|
|
216
|
+
# Private: The connection
|
217
|
+
#
|
218
|
+
# Returns the current connection to the device
|
219
|
+
#
|
170
220
|
def connection
|
171
221
|
@_connection ||= Airplay::Connection.new(@device)
|
172
222
|
end
|
173
223
|
|
224
|
+
# Private: The persistent connection
|
225
|
+
#
|
226
|
+
# Returns the persistent connection to the device
|
227
|
+
#
|
174
228
|
def persistent
|
175
229
|
@_persistent ||= Airplay::Connection.new(@device, keep_alive: true)
|
176
230
|
end
|
177
231
|
|
232
|
+
# Private: Starts checking for playback status ever 1 second
|
233
|
+
# Adds one timer to the pool
|
234
|
+
#
|
235
|
+
# Returns nothing
|
236
|
+
#
|
178
237
|
def check_for_playback_status
|
179
238
|
timers << every(1) do
|
180
239
|
case true
|
@@ -188,6 +247,8 @@ module Airplay
|
|
188
247
|
|
189
248
|
# Private: Get ready the state machine
|
190
249
|
#
|
250
|
+
# Returns nothing
|
251
|
+
#
|
191
252
|
def start_the_machine
|
192
253
|
@machine = MicroMachine.new(:loading)
|
193
254
|
|