airplayer 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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