airplay 1.0.0.beta3 → 1.0.0
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.
- 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
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -24,11 +24,11 @@ much as you want in: https://gumroad.com/l/airplay
|
|
|
24
24
|
|
|
25
25
|
### Library
|
|
26
26
|
|
|
27
|
-
`gem install airplay
|
|
27
|
+
`gem install airplay`
|
|
28
28
|
|
|
29
29
|
## CLI
|
|
30
30
|
|
|
31
|
-
`gem install airplay-cli
|
|
31
|
+
`gem install airplay-cli`
|
|
32
32
|
|
|
33
33
|
## Usage
|
|
34
34
|
|
|
@@ -37,6 +37,9 @@ much as you want in: https://gumroad.com/l/airplay
|
|
|
37
37
|
#### View devices
|
|
38
38
|
|
|
39
39
|
`air list`
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
40
43
|
```text
|
|
41
44
|
* Apple TV (AppleTV2,1 running 11A502)
|
|
42
45
|
ip: 192.168.1.12
|
|
@@ -46,6 +49,9 @@ much as you want in: https://gumroad.com/l/airplay
|
|
|
46
49
|
#### Play a video
|
|
47
50
|
|
|
48
51
|
`air play [url to video or local file]`
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
49
55
|
```text
|
|
50
56
|
Playing http://movietrailers.apple.com/movies/universal/rush/rush-tlr3_480p.mov?width=848&height=352
|
|
51
57
|
Time: 00:00:13 [===== ] 7% Apple TV
|
|
@@ -63,8 +69,8 @@ Time: 00:00:13 [===== ] 7% Apple TV
|
|
|
63
69
|
Airplay.configure do |config|
|
|
64
70
|
config.log_level # Log4r levels (Default: Log4r::ERROR)
|
|
65
71
|
config.autodiscover # Allows to search for nodes (Default: true)
|
|
66
|
-
config.host # In which host bind the server (Default: 0.0.0.0)
|
|
67
|
-
config.port # In which port bind the server (Default: 1337)
|
|
72
|
+
config.host # In which host to bind the server (Default: 0.0.0.0)
|
|
73
|
+
config.port # In which port to bind the server (Default: 1337)
|
|
68
74
|
config.output # Where to log (Default: Log4r::Outputter.stdout)
|
|
69
75
|
end
|
|
70
76
|
```
|
|
@@ -77,11 +83,15 @@ require "airplay"
|
|
|
77
83
|
Airplay.devices.each do |device|
|
|
78
84
|
puts device.name
|
|
79
85
|
end
|
|
86
|
+
```
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
#### Accesing and Grouping
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# You can access a known device easily
|
|
82
92
|
device = Airplay["Apple TV"]
|
|
83
93
|
|
|
84
|
-
# Or you can group known devices to have them do a given action
|
|
94
|
+
# Or you can group known devices to have them do a given action together
|
|
85
95
|
Airplay.group["Backyard"] << Airplay["Apple TV"]
|
|
86
96
|
Airplay.group["Backyard"] << Airplay["Room TV"]
|
|
87
97
|
|
|
@@ -165,8 +175,14 @@ player.progress -> progress {
|
|
|
165
175
|
}
|
|
166
176
|
```
|
|
167
177
|
|
|
178
|
+
## Documentation
|
|
179
|
+
|
|
180
|
+
All the documentation of the README can be found in the `doc` folder.
|
|
181
|
+
To generate an updated README based on the contents of `doc` please use `rake doc:generate`
|
|
182
|
+
|
|
168
183
|
## Contributors
|
|
169
184
|
|
|
170
185
|
* [sodabrew](http://github.com/sodabrew)
|
|
171
186
|
* [pote](http://github.com/pote)
|
|
172
|
-
|
|
187
|
+
* [janogonzalez](http://github.com/janogonzalez) - Who allowed me to release 1.0
|
|
188
|
+
from startech conf <3
|
data/Rakefile
CHANGED
|
@@ -84,7 +84,7 @@ task :test => [:spec]
|
|
|
84
84
|
|
|
85
85
|
namespace :doc do
|
|
86
86
|
task :generate do
|
|
87
|
-
structure = %w(header installation usage contributors)
|
|
87
|
+
structure = %w(header installation usage documentation contributors)
|
|
88
88
|
|
|
89
89
|
File.open("README.md", "w+") do |f|
|
|
90
90
|
structure.each { |part| f << File.read("doc/#{part}.md") + "\n" }
|
data/airplay-cli.gemspec
CHANGED
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.homepage = "http://github.com/elcuervo/airplay"
|
|
14
14
|
s.files = %w(bin/air lib/airplay/cli.rb)
|
|
15
15
|
|
|
16
|
-
s.add_dependency("airplay", "= 1.0.0
|
|
16
|
+
s.add_dependency("airplay", "= 1.0.0")
|
|
17
17
|
s.add_dependency("clap", "~> 1.0.0")
|
|
18
18
|
s.add_dependency("ruby-progressbar", "~> 1.2.0")
|
|
19
19
|
end
|
data/bin/air
CHANGED
|
@@ -2,26 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
require "clap"
|
|
4
4
|
require "airplay/cli"
|
|
5
|
+
require "airplay/cli/version"
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
begin
|
|
8
|
+
Clap.run ARGV,
|
|
9
|
+
"--device" => lambda { |device_name| @device = Airplay[device_name] },
|
|
10
|
+
"--wait" => lambda { |sec| @wait = sec.to_i },
|
|
11
|
+
"--interactive" => lambda { @interactive = true },
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
"list" => Airplay::CLI.method(:list),
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
options = { device: @device || Airplay.devices.first }
|
|
15
|
-
Airplay::CLI.play(video, options)
|
|
16
|
-
},
|
|
15
|
+
"version" => lambda { puts Airplay::CLI::VERSION },
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
"play" => lambda { |video|
|
|
18
|
+
options = { device: @device || Airplay.devices.first }
|
|
19
|
+
Airplay::CLI.play(video, options)
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"view" => lambda { |file|
|
|
23
|
+
options = {
|
|
24
|
+
device: @device || Airplay.devices.first,
|
|
25
|
+
interactive: @interactive || false,
|
|
26
|
+
wait: @wait || 3
|
|
27
|
+
}
|
|
28
|
+
Airplay::CLI.view(file, options)
|
|
23
29
|
}
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
rescue Airplay::Browser::NoDevicesFound
|
|
32
|
+
puts "No devices found."
|
|
33
|
+
rescue Interrupt
|
|
34
|
+
puts "Bye!"
|
|
35
|
+
end
|
|
26
36
|
|
|
27
37
|
# vim: ft=ruby
|
|
Binary file
|
|
Binary file
|
data/doc/usage.md
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
#### View devices
|
|
6
6
|
|
|
7
7
|
`air list`
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
8
11
|
```text
|
|
9
12
|
* Apple TV (AppleTV2,1 running 11A502)
|
|
10
13
|
ip: 192.168.1.12
|
|
@@ -14,6 +17,9 @@
|
|
|
14
17
|
#### Play a video
|
|
15
18
|
|
|
16
19
|
`air play [url to video or local file]`
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
17
23
|
```text
|
|
18
24
|
Playing http://movietrailers.apple.com/movies/universal/rush/rush-tlr3_480p.mov?width=848&height=352
|
|
19
25
|
Time: 00:00:13 [===== ] 7% Apple TV
|
|
@@ -31,8 +37,8 @@ Time: 00:00:13 [===== ] 7% Apple TV
|
|
|
31
37
|
Airplay.configure do |config|
|
|
32
38
|
config.log_level # Log4r levels (Default: Log4r::ERROR)
|
|
33
39
|
config.autodiscover # Allows to search for nodes (Default: true)
|
|
34
|
-
config.host # In which host bind the server (Default: 0.0.0.0)
|
|
35
|
-
config.port # In which port bind the server (Default: 1337)
|
|
40
|
+
config.host # In which host to bind the server (Default: 0.0.0.0)
|
|
41
|
+
config.port # In which port to bind the server (Default: 1337)
|
|
36
42
|
config.output # Where to log (Default: Log4r::Outputter.stdout)
|
|
37
43
|
end
|
|
38
44
|
```
|
|
@@ -45,11 +51,15 @@ require "airplay"
|
|
|
45
51
|
Airplay.devices.each do |device|
|
|
46
52
|
puts device.name
|
|
47
53
|
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Accesing and Grouping
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
```ruby
|
|
59
|
+
# You can access a known device easily
|
|
50
60
|
device = Airplay["Apple TV"]
|
|
51
61
|
|
|
52
|
-
# Or you can group known devices to have them do a given action
|
|
62
|
+
# Or you can group known devices to have them do a given action together
|
|
53
63
|
Airplay.group["Backyard"] << Airplay["Apple TV"]
|
|
54
64
|
Airplay.group["Backyard"] << Airplay["Room TV"]
|
|
55
65
|
|
data/lib/airplay.rb
CHANGED
|
@@ -8,22 +8,44 @@ require "airplay/version"
|
|
|
8
8
|
#
|
|
9
9
|
module Airplay
|
|
10
10
|
class << self
|
|
11
|
+
# Public: General configuration
|
|
12
|
+
#
|
|
13
|
+
# &block - The block that will modify the configuration
|
|
14
|
+
#
|
|
15
|
+
# Returns the configuration file.
|
|
16
|
+
#
|
|
11
17
|
def configure(&block)
|
|
12
18
|
yield(configuration) if block
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
# Public: Access the server object
|
|
22
|
+
#
|
|
23
|
+
# Returns the Server object
|
|
24
|
+
#
|
|
15
25
|
def server
|
|
16
26
|
@_server ||= Server.new
|
|
17
27
|
end
|
|
18
28
|
|
|
29
|
+
# Public: Browses for devices in the current network
|
|
30
|
+
#
|
|
31
|
+
# Returns nothing.
|
|
32
|
+
#
|
|
19
33
|
def browse
|
|
20
34
|
browser.browse
|
|
21
35
|
end
|
|
22
36
|
|
|
37
|
+
# Public: Access or create a group based on a key
|
|
38
|
+
#
|
|
39
|
+
# Returns the Hash object.
|
|
40
|
+
#
|
|
23
41
|
def group
|
|
24
42
|
@_group ||= Hash.new { |h, k| h[k] = Group.new(k) }
|
|
25
43
|
end
|
|
26
44
|
|
|
45
|
+
# Public: Helper method to access all the devices
|
|
46
|
+
#
|
|
47
|
+
# Returns a Group with all the devices.
|
|
48
|
+
#
|
|
27
49
|
def all
|
|
28
50
|
@_all ||= begin
|
|
29
51
|
group = Group.new(:all)
|
|
@@ -32,23 +54,39 @@ module Airplay
|
|
|
32
54
|
end
|
|
33
55
|
end
|
|
34
56
|
|
|
35
|
-
# Public: Lists found devices
|
|
57
|
+
# Public: Lists found devices if autodiscover is enabled
|
|
58
|
+
#
|
|
59
|
+
# Returns an Array with all the devices
|
|
36
60
|
#
|
|
37
61
|
def devices
|
|
38
62
|
browse if browser.devices.empty? && configuration.autodiscover
|
|
39
63
|
browser.devices
|
|
40
64
|
end
|
|
41
65
|
|
|
66
|
+
# Public: Access the configuration object
|
|
67
|
+
#
|
|
68
|
+
# Returns the Configuration object
|
|
69
|
+
#
|
|
42
70
|
def configuration
|
|
43
71
|
@_configuration ||= Configuration.new
|
|
44
72
|
end
|
|
45
73
|
|
|
74
|
+
# Public: Access a device by name
|
|
75
|
+
#
|
|
76
|
+
# device_name - The name to search on the devices
|
|
77
|
+
#
|
|
78
|
+
# Returns the found device
|
|
79
|
+
#
|
|
46
80
|
def [](device_name)
|
|
47
81
|
devices.find_by_name(device_name)
|
|
48
82
|
end
|
|
49
83
|
|
|
50
84
|
private
|
|
51
85
|
|
|
86
|
+
# Private: Access the browser object
|
|
87
|
+
#
|
|
88
|
+
# Returns the momoized Browser object
|
|
89
|
+
#
|
|
52
90
|
def browser
|
|
53
91
|
@_browser ||= Browser.new
|
|
54
92
|
end
|
data/lib/airplay/browser.rb
CHANGED
|
@@ -8,6 +8,8 @@ module Airplay
|
|
|
8
8
|
# Public: Browser class to find Airplay-enabled devices in the network
|
|
9
9
|
#
|
|
10
10
|
class Browser
|
|
11
|
+
NoDevicesFound = Class.new(StandardError)
|
|
12
|
+
|
|
11
13
|
SEARCH = "_airplay._tcp."
|
|
12
14
|
|
|
13
15
|
def initialize
|
|
@@ -16,6 +18,8 @@ module Airplay
|
|
|
16
18
|
|
|
17
19
|
# Public: Browses in the search of devices and adds them to the nodes
|
|
18
20
|
#
|
|
21
|
+
# Returns nothing or raises NoDevicesFound if there are no devices
|
|
22
|
+
#
|
|
19
23
|
def browse
|
|
20
24
|
timeout(5) do
|
|
21
25
|
DNSSD.browse!(SEARCH) do |node|
|
|
@@ -23,10 +27,14 @@ module Airplay
|
|
|
23
27
|
break unless node.flags.more_coming?
|
|
24
28
|
end
|
|
25
29
|
end
|
|
30
|
+
rescue Timeout::Error => e
|
|
31
|
+
raise NoDevicesFound
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
# Public: Access to the node list
|
|
29
35
|
#
|
|
36
|
+
# Returns the Devices list object
|
|
37
|
+
#
|
|
30
38
|
def devices
|
|
31
39
|
@_devices ||= Devices.new
|
|
32
40
|
end
|
|
@@ -35,8 +43,8 @@ module Airplay
|
|
|
35
43
|
|
|
36
44
|
# Private: Resolves a node given a node and a resolver
|
|
37
45
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
46
|
+
# node - The given node
|
|
47
|
+
# resolver - The DNSSD::Server that is resolving nodes
|
|
40
48
|
#
|
|
41
49
|
# Returns if there are more nodes coming
|
|
42
50
|
#
|
|
@@ -56,7 +64,9 @@ module Airplay
|
|
|
56
64
|
|
|
57
65
|
# Private: Resolves the node information given a node
|
|
58
66
|
#
|
|
59
|
-
#
|
|
67
|
+
# node - The node from the DNSSD browsing
|
|
68
|
+
#
|
|
69
|
+
# Returns nothing
|
|
60
70
|
#
|
|
61
71
|
def resolve(node)
|
|
62
72
|
resolver = DNSSD::Service.new
|
data/lib/airplay/cli.rb
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
require "airplay"
|
|
2
1
|
require "ruby-progressbar"
|
|
2
|
+
require "airplay"
|
|
3
|
+
require "airplay/cli/image_viewer"
|
|
3
4
|
|
|
5
|
+
# Public: Airplay core module
|
|
6
|
+
#
|
|
4
7
|
module Airplay
|
|
8
|
+
# Public: Airplay CLI module
|
|
9
|
+
#
|
|
5
10
|
module CLI
|
|
6
11
|
class << self
|
|
12
|
+
# Public: Lists all the devices to STDOUT
|
|
13
|
+
#
|
|
14
|
+
# Returns nothing.
|
|
15
|
+
#
|
|
7
16
|
def list
|
|
8
17
|
Airplay.devices.each do |device|
|
|
9
18
|
puts <<-EOS.gsub(/^\s{12}/,'')
|
|
@@ -15,6 +24,14 @@ module Airplay
|
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
|
|
27
|
+
# Public: Plays a video given a device
|
|
28
|
+
#
|
|
29
|
+
# video - The url or file path to the video
|
|
30
|
+
# options - Options that include the device
|
|
31
|
+
# * device: The device in which it should run
|
|
32
|
+
#
|
|
33
|
+
# Returns nothing.
|
|
34
|
+
#
|
|
18
35
|
def play(video, options)
|
|
19
36
|
device = options[:device]
|
|
20
37
|
player = device.play(video)
|
|
@@ -31,74 +48,34 @@ module Airplay
|
|
|
31
48
|
player.wait
|
|
32
49
|
end
|
|
33
50
|
|
|
51
|
+
# Public: Show an image given a device
|
|
52
|
+
#
|
|
53
|
+
# file_or_dir - The url, file path or folder path to the image/s
|
|
54
|
+
# options - Options that include the device
|
|
55
|
+
# * device: The device in which it should run
|
|
56
|
+
# * interactive: Boolean flag to control playback with the
|
|
57
|
+
# arrow keys
|
|
58
|
+
#
|
|
59
|
+
# Returns nothing.
|
|
60
|
+
#
|
|
34
61
|
def view(file_or_dir, options)
|
|
35
62
|
device = options[:device]
|
|
36
|
-
|
|
63
|
+
viewer = ImageViewer.new(device, options)
|
|
37
64
|
|
|
38
65
|
if File.directory?(file_or_dir)
|
|
39
66
|
files = Dir.glob("#{file_or_dir}/*")
|
|
40
67
|
|
|
41
68
|
if options[:interactive]
|
|
42
|
-
|
|
69
|
+
viewer.interactive(files)
|
|
43
70
|
else
|
|
44
|
-
|
|
71
|
+
viewer.slideshow(files)
|
|
45
72
|
end
|
|
46
73
|
else
|
|
47
|
-
|
|
74
|
+
viewer.view(file_or_dir)
|
|
48
75
|
sleep
|
|
49
76
|
end
|
|
50
77
|
end
|
|
51
78
|
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def view_interactive(files)
|
|
55
|
-
numbers = Array(0...files.count)
|
|
56
|
-
transition = "None"
|
|
57
|
-
|
|
58
|
-
i = 0
|
|
59
|
-
loop do
|
|
60
|
-
view_image(device, files[i], transition)
|
|
61
|
-
|
|
62
|
-
case read_char
|
|
63
|
-
# Right Arrow
|
|
64
|
-
when "\e[C"
|
|
65
|
-
i = i + 1 > numbers.count - 1 ? 0 : i + 1
|
|
66
|
-
transition = "SlideLeft"
|
|
67
|
-
when "\e[D"
|
|
68
|
-
i = i - 1 < 0 ? numbers.count - 1 : i - 1
|
|
69
|
-
transition = "SlideRight"
|
|
70
|
-
else
|
|
71
|
-
break
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def view_slideshow(files)
|
|
77
|
-
files.each do |file|
|
|
78
|
-
view_image(device, file)
|
|
79
|
-
sleep wait
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def read_char
|
|
84
|
-
STDIN.echo = false
|
|
85
|
-
STDIN.raw!
|
|
86
|
-
|
|
87
|
-
input = STDIN.getc.chr
|
|
88
|
-
if input == "\e" then
|
|
89
|
-
input << STDIN.read_nonblock(3) rescue nil
|
|
90
|
-
input << STDIN.read_nonblock(2) rescue nil
|
|
91
|
-
end
|
|
92
|
-
ensure
|
|
93
|
-
STDIN.echo = true
|
|
94
|
-
STDIN.cooked!
|
|
95
|
-
|
|
96
|
-
return input
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def view_image(device, image, transition = "SlideLeft")
|
|
100
|
-
device.view(image, transition: transition)
|
|
101
|
-
end
|
|
102
79
|
end
|
|
103
80
|
end
|
|
104
81
|
end
|