airplayer 0.0.2 → 0.0.3

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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- airplayer (0.0.2)
4
+ airplayer (0.0.3)
5
5
  airplay
6
6
  rack
7
7
  ruby-progressbar
@@ -16,6 +16,13 @@ GEM
16
16
  net-http-persistent (>= 2.6)
17
17
  diff-lcs (1.1.3)
18
18
  dnssd (2.0)
19
+ guard (1.4.0)
20
+ listen (>= 0.4.2)
21
+ thor (>= 0.14.6)
22
+ guard-rspec (2.0.0)
23
+ guard (>= 1.1)
24
+ rspec (~> 2.11)
25
+ listen (0.5.3)
19
26
  net-http-digest_auth (1.2.1)
20
27
  net-http-persistent (2.7)
21
28
  rack (1.4.1)
@@ -36,5 +43,6 @@ PLATFORMS
36
43
 
37
44
  DEPENDENCIES
38
45
  airplayer!
46
+ guard-rspec
39
47
  rake
40
48
  rspec
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { 'spec' }
8
+ end
@@ -15,6 +15,7 @@ Command-line AirPlay video client for Apple TV
15
15
  Requirements
16
16
  -------------------------------------------------------------------------------
17
17
 
18
+ - OSX or Ubuntu
18
19
  - Ruby 1.9.2 or later
19
20
  - AppleTV 2G or later
20
21
 
@@ -50,30 +51,53 @@ Usage
50
51
  ### Play online video
51
52
 
52
53
  ```sh
53
- $ airplayer play http://example.com/my-video.mp4
54
- AirPlay: http://example.com/my-video.mp4 to Apple TV(10.0.1.18)
55
- Time: 00:00:31 |==============================================| 100% Complete
54
+ $ airplayer play http://heinlein.local/Movies/AKIRA.m4v
55
+
56
+ Source: http://heinlein.local/misc/Movies/AKIRA.m4v
57
+ Title: AKIRA.m4v
58
+ Device: Apple TV (10.0.1.2)
59
+ Time: 00:04:25 |= | 3% Streaming
56
60
  ```
57
61
 
58
- ### Play local video
62
+ ### Play video
59
63
 
60
64
  ```sh
61
- $ airplayer play $HOME/Movies/video.mp4
62
- AirPlay: http://10.0.1.13:7070 to Apple TV(10.0.1.18)
63
- Time: 00:00:31 |==============================================| 100% Complete
65
+ $ airplayer play '~/Movies/Trailers/007 SKYFALL.mp4'
66
+
67
+ Source: http://10.0.1.6:7070
68
+ Title: SKYFALL.mp4
69
+ Device: Apple TV (10.0.1.2)
70
+ Time: 00:00:20 |===== | 11% Streaming
64
71
  ```
65
72
 
73
+ ### Play all video in directory
74
+
75
+ ```sh
76
+ $ airplayer play ~/Movies/Trailers
77
+
78
+ Source: http://10.0.1.6:7070
79
+ Title: 007 Casino Royale.mp4
80
+ Device: Apple TV (10.0.1.2)
81
+ Time: 00:02:33 |==============================================| 100% Complete
82
+
83
+ Source: http://10.0.1.6:7070
84
+ Title: 007 Quantum Of Solace.mp4
85
+ Device: Apple TV (10.0.1.2)
86
+ Time: 00:02:01 |==============================================| 100% Complete
87
+
88
+ Source: http://10.0.1.6:7070
89
+ Title: 007 SKYFALL.mp4
90
+ Device: Apple TV (10.0.1.2)
91
+ Time: 00:02:36 |==============================================| 100% Complete
92
+ ```
66
93
 
67
94
  ### Repeat play
68
95
 
69
96
  ```sh
70
- $ airplayer play ./video.mp4 --repeat
71
- AirPlay: http://10.0.1.13:7070 to Apple TV(10.0.1.18)
72
- Time: 00:00:31 |===================== | 50% Streaming
97
+ $ airplayer play '~/Movies/Trailers/007 SKYFALL.mp4' --repeat
73
98
  ```
74
99
 
75
100
 
76
-
77
101
  LICENSE
78
102
  --------------------------------------------------------------------------------
79
103
 
@@ -22,4 +22,5 @@ Gem::Specification.new do |gem|
22
22
 
23
23
  gem.add_development_dependency 'rake'
24
24
  gem.add_development_dependency 'rspec'
25
+ gem.add_development_dependency 'guard-rspec'
25
26
  end
@@ -2,8 +2,10 @@ require 'rubygems'
2
2
  require 'airplayer/version'
3
3
 
4
4
  module AirPlayer
5
- autoload :Logger, 'airplayer/logger'
6
- autoload :App, 'airplayer/app'
7
- autoload :Player, 'airplayer/player'
8
- autoload :Server, 'airplayer/server'
5
+ autoload :Logger, 'airplayer/logger'
6
+ autoload :App, 'airplayer/app'
7
+ autoload :Controller, 'airplayer/controller'
8
+ autoload :Server, 'airplayer/server'
9
+ autoload :Playlist, 'airplayer/playlist'
10
+ autoload :Media, 'airplayer/media'
9
11
  end
@@ -2,10 +2,13 @@ require 'thor'
2
2
 
3
3
  module AirPlayer
4
4
  class App < Thor
5
- desc 'play <URI|FILE> [-r|--repeat]', 'Play video(URI or local video file path)'
6
- method_option :repeat, :aliases => '-r', :desc => 'Repeat play'
7
- def play(uri)
8
- Player.play(uri, options.repeat?)
5
+ desc 'play <URI|FILE|DIR> [-r|--repeat] ', 'Play video(URI or local video file path or video directory)'
6
+ method_option :repeat, :aliases => '-r', :desc => 'Repeat play', :type => :boolean
7
+ def play(target)
8
+ controller = Controller.new
9
+ Playlist.new.add(target).entries(options.repeat) do |media|
10
+ controller.play(media)
11
+ end
9
12
  end
10
13
 
11
14
  map '--version' => :version
@@ -0,0 +1,83 @@
1
+ require 'timeout'
2
+ require 'airplay'
3
+ require 'airplayer/progress_bar/base'
4
+
5
+ module AirPlayer
6
+ class Controller
7
+ BufferingTimeoutError = Class.new(TimeoutError)
8
+
9
+ def initialize
10
+ @airplay = Airplay::Client.new
11
+ @device = @airplay.browse.first
12
+ @player = nil
13
+ @progressbar = nil
14
+ @timeout = 30
15
+ @interval = 1
16
+ @total_sec = 0
17
+ @current_sec = 0
18
+ rescue Airplay::Client::ServerNotFoundError
19
+ abort '[ERROR] Apple device not found'
20
+ end
21
+
22
+ def play(media)
23
+ raise TypeError unless media.kind_of? Media
24
+
25
+ display_information(media)
26
+ @player = @airplay.send_video(media.open)
27
+
28
+ buffering
29
+ @progressbar.progress = @current_sec while playing
30
+
31
+ @progressbar.title = :Complete
32
+ pause
33
+ media.close
34
+ rescue BufferingTimeoutError
35
+ abort '[ERROR] Buffering timeout'
36
+ rescue TypeError
37
+ abort '[ERROR] Not media class'
38
+ end
39
+
40
+ def pause
41
+ @player.stop if @player
42
+ @progressbar.finish if @progressbar
43
+ end
44
+
45
+ def replay
46
+ @player.scrub(0)
47
+ @player.resume
48
+
49
+ @progressbar.reset
50
+ @progressbar.resume
51
+ end
52
+
53
+ private
54
+ def display_information(media)
55
+ puts
56
+ puts " Source: #{media.path}"
57
+ puts " Title: #{media.title}"
58
+ puts " Device: #{@device.name} (#{@device.ip})"
59
+
60
+ @progressbar = ProgressBar.create(:format => ' %a |%b%i| %p%% %t')
61
+ end
62
+
63
+ def buffering
64
+ timeout @timeout, BufferingTimeoutError do
65
+ @progressbar.title = :Buffering until playing
66
+ @progressbar.title = :Streaming
67
+ @progressbar.total = @total_sec
68
+ end
69
+ end
70
+
71
+ def playing
72
+ scrub = @player.scrub
73
+ @total_sec = scrub['duration']
74
+ @current_sec = scrub['position']
75
+ sleep @interval
76
+ progress?
77
+ end
78
+
79
+ def progress?
80
+ 0 < @current_sec && @current_sec < @total_sec
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,38 @@
1
+ require 'uri'
2
+
3
+ module AirPlayer
4
+ class Media
5
+ attr_reader :title, :path, :type
6
+
7
+ def initialize(target)
8
+ path = File.expand_path(target)
9
+ @title = File.basename(path)
10
+
11
+ if File.exist? path
12
+ @video_server = AirPlayer::Server.new(path)
13
+ @path = @video_server.uri
14
+ @type = :file
15
+ else
16
+ @path = URI.encode(target)
17
+ @type = :url
18
+ end
19
+ end
20
+
21
+ def open
22
+ Thread.start { @video_server.start } if file?
23
+ @path
24
+ end
25
+
26
+ def close
27
+ @video_server.stop if file?
28
+ end
29
+
30
+ def file?
31
+ @type == :file
32
+ end
33
+
34
+ def url?
35
+ @type == :url
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ module AirPlayer
2
+ class Playlist < Array
3
+ def add(item)
4
+ path = File.expand_path(item)
5
+ Dir.exists?(path) ? concat(media_in(path)) : push(Media.new(item))
6
+ self
7
+ end
8
+
9
+ def entries(repeat = false, &blk)
10
+ loop do
11
+ send(:each, &blk)
12
+ break unless repeat
13
+ end
14
+ end
15
+
16
+ private
17
+ def media_in(path)
18
+ Dir.entries(path).map do |node|
19
+ media_path = File.expand_path(node, path)
20
+ Media.new(media_path) if File.file? media_path
21
+ end.compact
22
+ end
23
+ end
24
+ end
@@ -18,18 +18,23 @@ module AirPlayer
18
18
  $stderr = STDERR
19
19
  end
20
20
 
21
- # networking - Getting the Hostname or IP in Ruby on Rails - Stack Overflow
22
- # http://stackoverflow.com/questions/42566/getting-the-hostname-or-ip-in-ruby-on-rails
23
- def local_ip
24
- # turn off reverse DNS resolution temporarily
25
- orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
21
+ def stop
22
+ @server.server.shutdown
23
+ end
24
+
25
+ private
26
+ # networking - Getting the Hostname or IP in Ruby on Rails - Stack Overflow
27
+ # http://stackoverflow.com/questions/42566/getting-the-hostname-or-ip-in-ruby-on-rails
28
+ def local_ip
29
+ # turn off reverse DNS resolution temporarily
30
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
26
31
 
27
- UDPSocket.open do |s|
28
- s.connect('8.8.8.8', 1)
29
- s.addr.last
32
+ UDPSocket.open do |s|
33
+ s.connect('8.8.8.8', 1)
34
+ s.addr.last
35
+ end
36
+ ensure
37
+ Socket.do_not_reverse_lookup = orig
30
38
  end
31
- ensure
32
- Socket.do_not_reverse_lookup = orig
33
- end
34
39
  end
35
40
  end
@@ -1,3 +1,3 @@
1
1
  module AirPlayer
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -1,17 +1,52 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe AirPlayer do
4
- let(:app) { AirPlayer::App.new }
3
+ describe :AirPlayer do
4
+ context :App do
5
+ let(:app) { AirPlayer::App.new }
5
6
 
6
- context 'App' do
7
7
  it 'class type is Thor' do
8
8
  expect(app).to be_kind_of Thor
9
9
  end
10
10
  end
11
11
 
12
- context 'Logger' do
12
+ context :Logger do
13
13
  it 'file path is /tmp/airplayer-access.log on Linux' do
14
- expect(AirPlayer::Logger.path).to eq '/tmp/airplayer-access.log'
14
+ if RbConfig::CONFIG['target_os'] =~ /linux|unix/
15
+ expect(AirPlayer::Logger.path).to eq '/tmp/airplayer-access.log'
16
+ end
17
+ end
18
+ end
19
+
20
+ context :Media do
21
+ it 'give to local file' do
22
+ media = AirPlayer::Media.new('./Gemfile')
23
+ expect(media.file?).to be_true
24
+ end
25
+ it 'give to url' do
26
+ media = AirPlayer::Media.new('http://example.com/video.mp4')
27
+ expect(media.url?).to be_true
28
+ end
29
+ end
30
+
31
+ context :Playlist do
32
+ let(:playlist) { AirPlayer::Playlist.new }
33
+
34
+ it 'add URL to list, and that media type is url' do
35
+ playlist.add('http://example.com/video.mp4')
36
+ expect(playlist.first.path).to match 'http'
37
+ end
38
+
39
+ it 'add file to list, and that media type is file' do
40
+ expect(playlist.add('./LICENSE').size).to eq 1
41
+ expect(playlist.add('./Gemfile').size).to eq 2
42
+ expect(playlist.first.file?).to be_true
43
+ end
44
+
45
+ it 'has list contains url or file path' do
46
+ playlist.add('../airplayer')
47
+ playlist.entries do |media|
48
+ expect(media).to be_kind_of AirPlayer::Media
49
+ end
15
50
  end
16
51
  end
17
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airplayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
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: 2012-09-27 00:00:00.000000000 Z
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: guard-rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
110
126
  description: Command-line AirPlay video client for Apple TV
111
127
  email:
112
128
  - tomohiro.t@gmail.com
@@ -120,6 +136,7 @@ files:
120
136
  - .travis.yml
121
137
  - Gemfile
122
138
  - Gemfile.lock
139
+ - Guardfile
123
140
  - LICENSE
124
141
  - README.markdown
125
142
  - Rakefile
@@ -127,8 +144,10 @@ files:
127
144
  - bin/airplayer
128
145
  - lib/airplayer.rb
129
146
  - lib/airplayer/app.rb
147
+ - lib/airplayer/controller.rb
130
148
  - lib/airplayer/logger.rb
131
- - lib/airplayer/player.rb
149
+ - lib/airplayer/media.rb
150
+ - lib/airplayer/playlist.rb
132
151
  - lib/airplayer/progress_bar/base.rb
133
152
  - lib/airplayer/server.rb
134
153
  - lib/airplayer/version.rb
@@ -148,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
148
167
  version: '0'
149
168
  segments:
150
169
  - 0
151
- hash: 2589441194092994510
170
+ hash: 1247565518444440084
152
171
  required_rubygems_version: !ruby/object:Gem::Requirement
153
172
  none: false
154
173
  requirements:
@@ -157,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
176
  version: '0'
158
177
  segments:
159
178
  - 0
160
- hash: 2589441194092994510
179
+ hash: 1247565518444440084
161
180
  requirements: []
162
181
  rubyforge_project:
163
182
  rubygems_version: 1.8.23
@@ -1,105 +0,0 @@
1
- require 'timeout'
2
- require 'uri'
3
- require 'airplay'
4
- require 'airplayer/progress_bar/base'
5
-
6
- module AirPlayer
7
- class Player
8
- BufferingTimeoutError = Class.new(TimeoutError)
9
-
10
- def self.play(*opts)
11
- new.play(*opts)
12
- rescue Airplay::Client::ServerNotFoundError
13
- abort '[ERROR] Apple device not found'
14
- end
15
-
16
- def initialize
17
- @airplay = Airplay::Client.new
18
- @player = nil
19
- @progressbar = nil
20
- @timeout = 30
21
- @interval = 1
22
- @total_sec = 0
23
- @current_sec = 0
24
- end
25
-
26
- def play(target, repeat = false)
27
- path = File.expand_path(target)
28
- if local_file? path
29
- video_server = AirPlayer::Server.new(path)
30
- uri = video_server.uri
31
- Thread.start { video_server.start }
32
- else
33
- uri = URI.encode(target)
34
- end
35
-
36
- device = @airplay.browse.first
37
- puts "AirPlay: #{uri} to #{device.name}(#{device.ip})"
38
- @progressbar = ProgressBar.create(:format => ' %a |%b%i| %p%% %t', :title => :Waiting)
39
- @player = @airplay.send_video(uri)
40
-
41
- loop do
42
- buffering
43
- @progressbar.progress = @current_sec while playing
44
- break unless repeat
45
- reset
46
- end
47
-
48
- stop
49
- rescue BufferingTimeoutError
50
- abort '[ERROR] Buffering timeout'
51
- end
52
-
53
- def reset
54
- @player.scrub(0)
55
- @player.resume
56
-
57
- @progressbar.reset
58
- @progressbar.resume
59
- end
60
-
61
- def stop
62
- unless @player.nil?
63
- @player.stop
64
- end
65
-
66
- unless @progressbar.nil?
67
- @progressbar.title = :Complete
68
- @progressbar.finish
69
- end
70
- end
71
-
72
- private
73
- def local_file?(path)
74
- File.exist? path
75
- end
76
-
77
- def buffering
78
- timeout @timeout, BufferingTimeoutError do
79
- loop do
80
- playing
81
- redo unless buffering?
82
- @progressbar.title = :Streaming
83
- @progressbar.total = @total_sec
84
- break
85
- end
86
- end
87
- end
88
-
89
- def playing
90
- scrub = @player.scrub
91
- @total_sec = scrub['duration']
92
- @current_sec = scrub['position']
93
- sleep @interval
94
- progress?
95
- end
96
-
97
- def progress?
98
- 0 < @current_sec && @current_sec < @total_sec
99
- end
100
-
101
- def buffering?
102
- 0 < @total_sec && 0 < @current_sec
103
- end
104
- end
105
- end