ektoplayer 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 55540b5adfb0dec28702908ee968d434b802221a
4
- data.tar.gz: 5f11aef936c061803e546ea22a431193fa106685
3
+ metadata.gz: 6d45cf3f25e3f52e0d259b0396903d2bee9ae811
4
+ data.tar.gz: cee36a784279f4b0698adadfa8fb99df16b979be
5
5
  SHA512:
6
- metadata.gz: '098d4f6de14917ac00d6321729827ca49469628b26d9a1b16ba3054660886893f6f5e7ef1837cdc3ebacae2703b8b56e835d6ecec5243ec5ee45bccc5ab8dff8'
7
- data.tar.gz: 1bf895998e866f025d9b6d13775c0d32a9352c21a0aaa7fa6d4743885775444f43a741f86fc1a8288d8251d3ab35e7883aac23eb92471ab87df3e945c0d9ba63
6
+ metadata.gz: 82df89a2a491c4171a7f91f0d46d42e630d1ab1f942785a02ae570d13ecd9ac6f993eddfe720555c0b8bb256810c4d43a96bd2a6da5e24721b5123209e454bab
7
+ data.tar.gz: a553baadb74b75f7053a7dca15bb2305887482ff9596d04fb70acc68b4b22415655480d70a2554da70995c4caeafb0ed0cde08f19acc58093c54af627f7d933c
data/README.md CHANGED
@@ -14,6 +14,7 @@ It allows you to
14
14
  * Vi-like keybindings (`hjkl`, `^d`, `^u`, `/`, `?`, `n`, `N`, ...)
15
15
  * Up to 256 colors are supported
16
16
  * Local sound file cache
17
+ * Song prefetching
17
18
 
18
19
  ## Screenshots
19
20
 
@@ -44,6 +45,10 @@ library to compile the native extensions.
44
45
  apt-get install ruby ruby-dev portaudio19-dev libmpg123-dev sqlite3 libsqlite3-dev libncurses-dev libz1g-dev build-essential
45
46
  gem install ektoplayer
46
47
 
48
+ ## Configuration
49
+
50
+ Have a look at the default [ektoplayer.rc](https://github.com/braph/ektoplayer/blob/master/doc/ektoplayer.rc).
51
+
47
52
  ## Authors
48
53
 
49
54
  * [Benjamin Abendroth](https://github.com/braph)
@@ -10,7 +10,7 @@ require 'date'
10
10
 
11
11
  module Ektoplayer
12
12
  class Application
13
- VERSION = '0.1.4'.freeze
13
+ VERSION = '0.1.5'.freeze
14
14
  GITHUB_URL = 'https://github.com/braph/ektoplayer'.freeze
15
15
  EKTOPLAZM_URL = 'http://www.ektoplazm.com'.freeze
16
16
 
@@ -35,7 +35,7 @@ module Ektoplayer
35
35
 
36
36
  def run
37
37
  #Thread.abort_on_exception=(true)
38
- Thread.report_on_exception=(true) rescue nil
38
+ Thread.report_on_exception=(true) if Thread.public_method_defined? :report_on_exception
39
39
 
40
40
  # make each configuration object globally accessible as a singleton
41
41
  [Config, Bindings, Theme].each { |c| Common::mksingleton(c) }
@@ -121,14 +121,18 @@ module Ektoplayer
121
121
 
122
122
  if Config[:prefetch]
123
123
  trackloader_mutex = Mutex.new
124
+ @prefetch_thread = nil
124
125
  player.events.on(:position_change) do
125
- Thread.new do
126
- if player.length > 30 and player.position_percent > 0.8
126
+ @prefetch_thread ||= Thread.new do
127
+ if player.length > 30 and player.position_percent > 0.7
127
128
  trackloader_mutex.synchronize do
128
129
  trackloader.get_track_file(playlist[playlist.get_next_pos]['url'])
129
- sleep 5
130
+ sleep 30
130
131
  end
131
132
  end
133
+
134
+ sleep 10
135
+ @prefetch_thread = nil
132
136
  end
133
137
  end
134
138
  end
@@ -135,7 +135,7 @@ module Ektoplayer
135
135
  # browser
136
136
  :'browser.add_to_playlist' => [' ', ?a ],
137
137
  :'browser.enter' => [?E, Curses::KEY_ENTER ],
138
- :'browser.back' => [?b, Curses::KEY_BACKSPACE ]},
138
+ :'browser.back' => [?B, Curses::KEY_BACKSPACE ]},
139
139
  help: {
140
140
  :'help.top' => [?g, Curses::KEY_HOME ],
141
141
  :'help.bottom' => [?G, Curses::KEY_END ],
@@ -155,7 +155,7 @@ module Ektoplayer
155
155
 
156
156
  @bindings.default_proc = proc { |h,k| fail "Unknown widget #{k}" }
157
157
  @bindings.each do |widget, hash|
158
- hash.default_proc = proc { |h,k| h[k] = {} }
158
+ hash.default_proc = proc { |h,k| h[k] = [] }
159
159
  hash.values.each do |keys|
160
160
  keys.map! { |key| parse_key(key) }
161
161
  end
@@ -181,6 +181,8 @@ module Ektoplayer
181
181
  key.to_sym
182
182
  elsif key.size == 2 and key.start_with?(?^)
183
183
  Curses.const_get("KEY_CTRL_#{key[1].upcase}")
184
+ elsif key =~ /^(key_)?space$/i
185
+ :' '
184
186
  else
185
187
  key = key.upcase.tr(?-, ?_)
186
188
  key = "KEY_#{key}" unless key.start_with?('KEY_')
@@ -192,9 +194,9 @@ module Ektoplayer
192
194
 
193
195
  def bind(widget, key, command)
194
196
  widget, command = widget.to_sym, command.to_sym
195
- fail "Unknown command #{command}" unless COMMANDS.include? command
197
+ fail "Unknown command #{command}" unless @commands.include? command
196
198
 
197
- @bindings[widget][command].delete parse_key(key)
199
+ @bindings[widget][command].delete parse_key(key) rescue nil
198
200
  @bindings[widget][command] << parse_key(key)
199
201
  check_collisions
200
202
  end
@@ -205,6 +207,12 @@ module Ektoplayer
205
207
  end
206
208
  end
207
209
 
210
+ def unbind_all
211
+ @bindings.each do |widget, commands|
212
+ commands.clear
213
+ end
214
+ end
215
+
208
216
  def bind_view(section, view, view_operations, operations)
209
217
  @bindings[section.to_sym].each do |command, keys|
210
218
  keys.each do |key|
@@ -217,14 +225,20 @@ module Ektoplayer
217
225
  private def check_collisions
218
226
  global_keys = @bindings[:global].values.flatten
219
227
  global_keys.each do |k|
220
- fail "Double binding in 'global': #{k}" if global_keys.count(k) > 1
228
+ fail "Double binding in 'global', key #{keyname(k)}" if global_keys.count(k) > 1
221
229
  end
222
230
 
223
231
  @bindings.each_pair do |widget, commands|
232
+ next if widget == :global
224
233
  widget_keys = commands.values.flatten
225
234
  widget_keys.each do |k|
226
- fail "Double binding in '#{widget}': #{k}" if widget_keys.count(k) > 1
227
- fail "Double binding in 'global/#{widget}': #{k}" if global_keys.include? k
235
+ if widget_keys.count(k) > 1
236
+ fail "Double binding in '#{widget}', key `#{keyname(k)}`"
237
+ end
238
+
239
+ if global_keys.include? k
240
+ fail "Double binding in 'global <> #{widget}', key #{keyname(k)}"
241
+ end
228
242
  end
229
243
  end
230
244
  end
@@ -1,4 +1,43 @@
1
1
  require 'zip'
2
+ require 'thread'
3
+
4
+ class ConditionSignals
5
+ def initialize
6
+ @mutex, @cond = Mutex.new, ConditionVariable.new
7
+ @curr_signal = nil
8
+ @signal_hooks = {}
9
+
10
+ Thread.new do
11
+ @mutex.synchronize do
12
+ loop do
13
+ @cond.wait(@mutex)
14
+
15
+ if @signal_hooks.key? @curr_signal
16
+ @signal_hooks[@curr_signal].()
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def wait(name, timeout=nil)
24
+ @mutex.synchronize do
25
+ loop do
26
+ @cond.wait(@mutex, timeout)
27
+ return if @curr_signal == name
28
+ end
29
+ end
30
+ end
31
+
32
+ def signal(name)
33
+ @curr_signal = name
34
+ @cond.broadcast
35
+ end
36
+
37
+ def on(name, &block)
38
+ @signal_hooks[name] = block
39
+ end
40
+ end
2
41
 
3
42
  class Object
4
43
  alias :frz :freeze
@@ -2,7 +2,7 @@ unless Array.public_method_defined? :sum
2
2
  class Array
3
3
  def sum
4
4
  result = 0
5
- self.each { |i| result += 1 }
5
+ self.each { |i| result += i }
6
6
  result
7
7
  end
8
8
  end
@@ -52,20 +52,20 @@ module Ektoplayer
52
52
 
53
53
  DEFAULT_PLAYLIST_FORMAT = (
54
54
  '<number size="3" fg="magenta" />' +
55
- '<artist rel="33" fg="blue" />' +
56
- '<album rel="33" fg="red" />' +
55
+ '<artist rel="25" fg="blue" />' +
56
+ '<album rel="30" fg="red" />' +
57
57
  '<title rel="33" fg="yellow" />' +
58
58
  '<styles rel="20" fg="cyan" />' +
59
59
  '<bpm size="4" fg="green" justify="right" />').freeze
60
60
 
61
61
  DEFAULT_PLAYINGINFO_FORMAT1 =
62
- '<text fg="black">──┤ </text><title bold="on" fg="yellow" /><text fg="black"> ├──</text>'.freeze
62
+ '<text fg="black">&lt;&lt; </text><title bold="on" fg="yellow" /><text fg="black"> &gt;&gt;</text>'.freeze
63
63
 
64
64
  DEFAULT_PLAYINGINFO_FORMAT2 =
65
65
  '<artist bold="on" fg="blue" /><text> - </text><album bold="on" fg="red" /><text> (</text><date fg="cyan" /><text>)</text>'.freeze
66
66
 
67
67
  def register(key, description, default, method=nil)
68
- @doc[key.to_sym] = description.squeeze(' ').freeze
68
+ # parameter `description` is used by tools/mkconfig.rb, but not here
69
69
 
70
70
  if method
71
71
  @cast[key.to_sym] = method if method
@@ -78,23 +78,24 @@ module Ektoplayer
78
78
 
79
79
  def initialize
80
80
  @options = Hash.new { |h,k| fail "Unknown option #{k}" }
81
- @doc, @cast = {}, {}
81
+ @cast = {}
82
82
 
83
- reg :database_file, 'File to store metadata',
83
+ reg :database_file, 'Database file for storing ektoplazm metadata',
84
84
  File.join(CONFIG_DIR, 'meta.db'),
85
85
  File.method(:expand_path)
86
86
 
87
- reg :log_file, 'Log file',
87
+ reg :log_file, 'File used for logging',
88
88
  File.join(CONFIG_DIR, 'ektoplayer.log'),
89
89
  File.method(:expand_path)
90
90
 
91
- reg :temp_dir, %{Where to temporary store mp3 files. Directory will be created
92
- if it does not exist (parent directories will NOT be created},
91
+ reg :temp_dir, %{Temporary dir for downloading mp3 files. They will be moved to `cache_dir`
92
+ after the download completed and was successful.
93
+ Directory will be created if it does not exist, parent directories will not be created.},
93
94
  '/tmp/.ektoplazm',
94
95
  File.method(:expand_path)
95
96
 
96
97
  reg :cache_dir,
97
- 'Directory for storing mp3 files',
98
+ 'Directory for storing cached mp3 files',
98
99
  File.join(Dir.home, '.cache', 'ektoplayer'),
99
100
  File.method(:expand_path)
100
101
 
@@ -109,18 +110,21 @@ module Ektoplayer
109
110
 
110
111
  reg :auto_extract_to_archive_dir,
111
112
  %{Enable/disable automatic extraction of downloaded MP3
112
- archives from download_dir to archive_dir}, true
113
+ archives from `download_dir' to `archive_dir'}, true
113
114
 
114
115
  reg :delete_after_extraction,
115
- %{In combination with auto_extract_to_archive_dir:
116
+ %{In combination `with auto_extract_to_archive_dir':
116
117
  Delete zip archive after successful extraction}, true
117
118
 
118
119
  reg :playlist_load_newest,
119
120
  %{How many tracks from database should be added to
120
- the playlist on application start}, 100
121
+ the playlist on application start.}, 100
121
122
 
122
123
  reg :use_cache,
123
- 'Enable/disable local mp3 cache', true
124
+ %{Enable/disable local mp3 cache.
125
+ If this option is disabled, the downloaded mp3 files won't be moved
126
+ from `cache_dir`. Instead they will reside in `temp_dir` and will
127
+ be deleted on application exit.}, true
124
128
 
125
129
  reg :prefetch,
126
130
  'Enable prefetching next track do be played', true
@@ -149,24 +153,21 @@ module Ektoplayer
149
153
  reg 'progressbar.display',
150
154
  'Enable/disable progressbar', true
151
155
 
152
- reg 'progressbar.download_char',
153
- 'Character used for displaying download progress', ?-
154
-
155
156
  reg 'progressbar.progress_char',
156
- 'Character used for displaying playing progress', '─'
157
+ 'Character used for displaying playing progress', ?~
157
158
 
158
159
  reg 'progressbar.rest_char',
159
- 'Character used for the rest of the line', '─'
160
+ 'Character used for the rest of the line', ?~
160
161
 
161
162
  # - Volumemeter
162
163
  reg 'volumemeter.display',
163
164
  'Enable/disable volumemeter', true
164
165
 
165
166
  reg 'volumemeter.level_char',
166
- 'Character used for displaying volume level', '·'
167
+ 'Character used for displaying volume level', ?~
167
168
 
168
169
  reg 'volumemeter.rest_char',
169
- 'Character used for the rest of the line', '·'
170
+ 'Character used for the rest of the line', ?~
170
171
 
171
172
  # - Playinginfo
172
173
  reg 'playinginfo.display',
@@ -225,11 +226,12 @@ module Ektoplayer
225
226
  set: self.method(:set),
226
227
  bind: bindings.method(:bind),
227
228
  unbind: bindings.method(:unbind),
229
+ unbind_all: bindings.method(:unbind_all),
228
230
  color: theme.method(:color),
229
231
  color_256: theme.method(:color_256),
230
232
  color_mono: theme.method(:color_mono)
231
233
  }
232
- callbacks.default_proc = proc { |h,k| fail "unknown command: #{k}" }
234
+ callbacks.default_proc = proc { fail 'unknown command' }
233
235
  callbacks.freeze
234
236
 
235
237
  open(file, ?r).readlines.each do |line|
@@ -239,10 +241,10 @@ module Ektoplayer
239
241
 
240
242
  begin
241
243
  cb = callbacks[command.to_sym]
242
- fail "missing arguments for #{command}" if args.size != cb.arity
243
244
  cb.call(*args)
245
+ #fail "Command '#{command}' given args: #{args.size}, wanted #{cb.arity}" if args.size != cb.arity
244
246
  rescue
245
- fail "#{file}:#{$.}: #{$!}"
247
+ fail "#{file}:#{$.}: #{command}: #{$!}"
246
248
  end
247
249
  end
248
250
  end
@@ -19,7 +19,7 @@ module Ektoplayer
19
19
  @trackloader.download_album(track['url']) rescue (
20
20
  Application.log(self, $!)
21
21
  )
22
- end
22
+ end.join(0.3) # prevent too many hits
23
23
  end
24
24
 
25
25
  def reload(index)
@@ -28,13 +28,15 @@ module Ektoplayer
28
28
  @trackloader.get_track_file(track['url'], reload: true) rescue (
29
29
  Application.log(self, $!)
30
30
  )
31
- end
31
+ end.join(0.3) # prevent too many hits
32
32
  end
33
33
 
34
34
  def play(index)
35
35
  return unless track = @playlist[index]
36
36
  @playlist.current_playing=(index)
37
- @player.play(@trackloader.get_track_file(track['url']))
37
+ Thread.new do
38
+ @player.play(@trackloader.get_track_file(track['url']))
39
+ end.join(0.3) # prevent too many hits
38
40
  end
39
41
 
40
42
  def play_next
@@ -2,7 +2,7 @@ require_relative 'ui/colors'
2
2
 
3
3
  module Ektoplayer
4
4
  class Theme
5
- attr_reader :current
5
+ attr_reader :current, :theme
6
6
 
7
7
  def initialize
8
8
  @current = 0
@@ -71,6 +71,7 @@ module Ektoplayer
71
71
  end
72
72
 
73
73
  def color(name, *defs, theme: 8)
74
+ defs.map! { |d| Integer(d) rescue d.to_sym }
74
75
  @theme[theme][name.to_sym] = defs.freeze
75
76
  end
76
77
 
@@ -81,7 +82,7 @@ module Ektoplayer
81
82
  def [](theme_def) UI::Colors.get(theme_def) end
82
83
 
83
84
  def use_colors(colors)
84
- fail ArgumentError, 'unknown theme' unless @theme[colors]
85
+ fail 'unknown theme' unless @theme[colors]
85
86
  @current = colors
86
87
 
87
88
  UI::Colors.reset
@@ -114,31 +114,44 @@ module Ektoplayer
114
114
  @file = File.open(filename, ?w)
115
115
  @progress = 0
116
116
  @error = nil
117
+ @tries = 3
117
118
  end
118
119
 
119
120
  def start!
120
- Application.log(self, 'starting download: ', @url)
121
+ Application.log(self, 'starting download:', @url)
121
122
 
122
123
  Thread.new do
123
124
  begin
124
- http = Net::HTTP.new(@url.host, @url.port)
125
-
126
- http.request(Net::HTTP::Get.new(@url.request_uri)) do |res|
127
- fail res.body unless res.code == '200'
128
-
129
- @total = res.header['Content-Length'].to_i
130
-
131
- res.read_body do |chunk|
132
- @progress += chunk.size
133
- @events.trigger(:progress, @progress)
134
- @file << chunk
125
+ loop do
126
+ begin
127
+ http = Net::HTTP.new(@url.host, @url.port)
128
+ @file.rewind
129
+ @progress, @total = 0, nil
130
+
131
+ http.request(Net::HTTP::Get.new(@url.request_uri)) do |res|
132
+ fail res.body unless res.code == '200'
133
+
134
+ @total = res.header['Content-Length'].to_i
135
+
136
+ res.read_body do |chunk|
137
+ @progress += chunk.size
138
+ @events.trigger(:progress, @progress)
139
+ @file << chunk
140
+ end
141
+ end
142
+
143
+ fail 'filesize mismatch' if @progress != @total
144
+ @file.flush
145
+ @events.trigger(:completed)
146
+ break
147
+ rescue
148
+ if (@tries -= 1) < 1
149
+ @events.trigger(:failed, (@error = $!))
150
+ break
151
+ end
152
+ Application.log(self, 'retrying failed DL', $!)
135
153
  end
136
154
  end
137
-
138
- fail 'filesize mismatch' if @progress != @total
139
- @events.trigger(:completed)
140
- rescue
141
- @events.trigger(:failed, (@error = $!))
142
155
  ensure
143
156
  @file.close
144
157
  end
@@ -32,7 +32,7 @@ module UI
32
32
  # as draw, layout and refresh) are executed once and only once at the
33
33
  # end of this function.
34
34
  def with_lock
35
- held_locks = lock; yield
35
+ lock; yield
36
36
  ensure
37
37
  unlock
38
38
  end
@@ -42,26 +42,45 @@ module UI
42
42
  end
43
43
 
44
44
  def unlock
45
- return unless (@lock.exit rescue nil) or not visible?
45
+ return unless (@lock.exit rescue nil)
46
46
 
47
- layout if @want >= WANT_LAYOUT
48
- draw if @want >= WANT_REDRAW
49
- if @want >= WANT_REFRESH
50
- Canvas.update_screen
47
+ if @want & WANT_LAYOUT > 0
48
+ layout;
49
+ @want ^= WANT_LAYOUT
50
+ end
51
+ return if not visible?
52
+
53
+ if @want & WANT_REDRAW > 0
54
+ draw
55
+ @want ^= WANT_REDRAW
51
56
  end
52
57
 
53
- @want = 0
58
+ if @want > 0 #& WANT_REFRESH > 0
59
+ Canvas.update_screen
60
+ @want ^= WANT_REFRESH
61
+ end
54
62
  end
55
63
 
56
- def display(force_refresh=false, force_redraw=false)
64
+ def display(force_refresh=false, force_redraw=false, force_layout=false)
65
+ if @want & WANT_LAYOUT > 0 or force_layout
66
+ layout;
67
+ @want ^= WANT_LAYOUT
68
+ end
57
69
  return if not visible?
58
- layout if @want >= WANT_LAYOUT
59
- draw if @want >= WANT_REDRAW or force_redraw
60
- refresh if @want >= WANT_REFRESH or force_refresh
70
+
71
+ if @want & WANT_REDRAW > 0 or force_redraw
72
+ draw
73
+ @want ^= WANT_REDRAW
74
+ end
75
+
76
+ if @want > 0 or force_refresh #WANT_REFRESH > 0 or force_refresh
77
+ refresh
78
+ @want ^= WANT_REFRESH
79
+ end
61
80
  end
62
81
 
63
- def want_redraw; @want |= WANT_REDRAW end
64
- def want_layout; @want |= WANT_LAYOUT end
82
+ def want_redraw; @want |= 3 end
83
+ def want_layout; @want = 7 end
65
84
  def want_refresh; @want |= WANT_REFRESH end
66
85
 
67
86
  def invisible?; !visible? end
data/lib/ektoplayer/ui.rb CHANGED
@@ -6,6 +6,8 @@ require_relative 'ui/colors'
6
6
  require_relative 'events'
7
7
 
8
8
  module UI
9
+ CONDITION_SIGNALS = ConditionSignals.new
10
+
9
11
  class WidgetSizeError < Exception; end
10
12
 
11
13
  class Canvas
@@ -36,19 +38,7 @@ module UI
36
38
  end
37
39
 
38
40
  def self.enable_resize_detection
39
- @@winch_mutex ||= Mutex.new
40
- @@winch_cond ||= ConditionVariable.new
41
-
42
- Signal.trap('WINCH') { @@winch_cond.signal }
43
-
44
- @@winch_thread ||= Thread.new do
45
- loop do
46
- @@winch_mutex.synchronize do
47
- @@winch_cond.wait(@@winch_mutex)
48
- self.on_winch
49
- end
50
- end
51
- end
41
+ Signal.trap('WINCH') { @@want_resize = true }
52
42
  end
53
43
 
54
44
  def self.widget; @@widget end
@@ -57,19 +47,6 @@ module UI
57
47
  def self.visible?; true end
58
48
  def self.inivsibile?; false end
59
49
 
60
- def self.on_winch
61
- h, w = IO.console.winsize()
62
- Curses.resizeterm(h, w)
63
- Curses.refresh
64
- @@widget.size=(Size.new(height: h, width: w)) if @@widget
65
- rescue UI::WidgetSizeError
66
- Curses.clear
67
- Curses.addstr('terminal too small!')
68
- Curses.refresh
69
- rescue
70
- nil
71
- end
72
-
73
50
  def self.sub(cls, **opts)
74
51
  @@widget ||= (widget = cls.new(parent: self, **opts))
75
52
  widget
@@ -82,20 +59,26 @@ module UI
82
59
 
83
60
  def self.update_screen(force_redraw=false)
84
61
  @@updating ||= Mutex.new
62
+ @@want_resize ||= false
85
63
 
86
64
  if @@updating.try_lock
87
- if force_redraw
88
- Curses.clear
89
- Curses.refresh
90
- end
91
-
92
65
  begin
93
- @@widget.display(true, force_redraw) if @@widget and (@@mode ||= :curses) == :curses
66
+ if @@want_resize
67
+ @@want_resize = false
68
+ h, w = IO.console.winsize()
69
+ Curses.resizeterm(h, w)
70
+ Curses.clear
71
+ Curses.refresh
72
+ @@widget.size=(Size.new(height: h, width: w)) if @@widget
73
+ @@widget.display(true, true, true) if @@widget
74
+ else
75
+ @@widget.display(true, force_redraw) if @@widget
76
+ end
94
77
  rescue UI::WidgetSizeError
95
78
  Curses.clear
96
79
  Curses.addstr('terminal too small!')
97
80
  rescue
98
- Application.log(self, $!)
81
+ Ektoplayer::Application.log(self, $!)
99
82
  end
100
83
 
101
84
  Curses.doupdate
@@ -166,13 +149,17 @@ module UI
166
149
  end
167
150
  end
168
151
  end
152
+ rescue
153
+ # getch() returned something weird that could not be chr()d
169
154
  end while @@readline_obj.active?
170
155
  end
171
156
  end
172
157
  end
173
158
 
174
159
  def self.readline(*args, **opts, &block)
175
- (@@readline_obj ||= ReadlineWindow.new).readline(*args, **opts, &block)
160
+ (@@readline_obj ||= ReadlineWindow.new).readline(*args, **opts) do
161
+ Canvas.class_variable_get('@@updating').synchronize { yield }
162
+ end
176
163
  end
177
164
  end
178
165
 
@@ -190,17 +177,18 @@ module UI
190
177
  @thread ||= Thread.new do
191
178
  window = Curses::Window.new(size.height, size.width, pos.y, pos.x)
192
179
 
180
+ rlt = Thread.new { Readline.readline(prompt, add_hist) }
193
181
  Readline.set_screen_size(size.height, size.width)
194
182
  Readline.delete_text
195
- rlt = Thread.new { Readline.readline(prompt, add_hist) }
183
+ @readline_in_write.read_nonblock(100) rescue nil
196
184
 
197
185
  while rlt.alive?
198
186
  window.erase
199
- buffer = "#{prompt}#{Readline.line_buffer}"
187
+ buffer = prompt + Readline.line_buffer.to_s
200
188
  window << buffer[(buffer.size - size.width).clamp(0, buffer.size)..-1]
201
189
  window.cursor=(Point.new(x: Readline.point + prompt.size, y: 0))
202
190
  window.refresh
203
- @mutex.synchronize { @cond.wait(@mutex, 0.2) }
191
+ CONDITION_SIGNALS.wait(:readline, 0.2)
204
192
  end
205
193
 
206
194
  window.clear
@@ -212,7 +200,7 @@ module UI
212
200
 
213
201
  def feed(c)
214
202
  @readline_in_write.write(c)
215
- @cond.signal
203
+ CONDITION_SIGNALS.signal(:readline)
216
204
  @thread = nil if c == ?\n
217
205
  end
218
206
  end
@@ -30,7 +30,7 @@ module Ektoplayer
30
30
  end
31
31
 
32
32
  @win.with_attr(Theme[:'help.command_name']) do
33
- @win.on_column(18).addstr("#{name}")
33
+ @win.on_column(18).addstr(name.to_s)
34
34
  end
35
35
 
36
36
  @win.with_attr(Theme[:'help.command_desc']) do
@@ -37,7 +37,7 @@ module Ektoplayer
37
37
 
38
38
  @win.from_left(START_TAG_VALUE)
39
39
  @win.with_attr(Theme[:'info.value']) do
40
- @win.addstr("#{value}")
40
+ @win.addstr(value.to_s)
41
41
  end
42
42
  end
43
43
 
@@ -48,7 +48,7 @@ module Ektoplayer
48
48
 
49
49
  @win.from_left(START_INFO_VALUE)
50
50
  @win.with_attr(Theme[:'info.value']) do
51
- @win.addstr("#{value}")
51
+ @win.addstr(value.to_s)
52
52
  end
53
53
  end
54
54
 
@@ -161,7 +161,7 @@ module Ektoplayer
161
161
  @trackloader.downloads.each do |dl|
162
162
  name = File.basename(dl.filename)
163
163
  percent = Float(dl.progress) / dl.total * 100
164
- percent = "%0.2f" % percent
164
+ percent = '%0.2f' % percent
165
165
  draw_download(name, ?( + percent + '%)', dl.error)
166
166
  end
167
167
  @win.next_line
@@ -11,11 +11,11 @@ module Ektoplayer
11
11
  def initialize(**opts)
12
12
  super(**opts)
13
13
 
14
- @playinginfo = sub(PlayingInfo)
15
- @progressbar = sub(ProgressBar)
16
- @volumemeter = sub(VolumeMeter)
17
- @tabbar = sub(TabBar)
18
- @windows = sub(UI::SwitchContainer)
14
+ @playinginfo = sub(PlayingInfo, size: @size.update(height: 2))
15
+ @progressbar = sub(ProgressBar, size: @size.update(height: 1))
16
+ @volumemeter = sub(VolumeMeter, size: @size.update(height: 1))
17
+ @tabbar = sub(TabBar, size: @size.update(height: 1))
18
+ @windows = sub(UI::SwitchContainer, size: @size.calc(height: -4))
19
19
  @help = @windows.sub(Help, visible: false)
20
20
  @info = @windows.sub(Info, visible: false)
21
21
  @splash = @windows.sub(Splash, visible: false)
@@ -29,8 +29,8 @@ module Ektoplayer
29
29
 
30
30
  Config[:'main.widgets'].each { |w| add(send(w)) }
31
31
 
32
- @windows.selected=(@splash)
33
32
  self.selected=(@windows)
33
+ @windows.selected=(@splash)
34
34
  end
35
35
 
36
36
  def layout
@@ -30,51 +30,42 @@ module Ektoplayer
30
30
 
31
31
  def length=(l)
32
32
  return if @length == l.to_i
33
-
34
- with_lock do
35
- @length = l.to_i
36
- draw_position_and_length
37
- end
33
+ @length = l.to_i
34
+ draw_position_and_length
38
35
  end
39
36
 
40
37
  def position=(p)
41
38
  return if @position == p.to_i
42
-
43
- with_lock do
44
- @position = p.to_i
45
- draw_position_and_length
46
- end
39
+ @position = p.to_i
40
+ draw_position_and_length
47
41
  end
48
42
 
49
43
  def draw_position_and_length
44
+ return unless visible?
50
45
  @win.setpos(0,0)
51
46
  @win.with_attr(Theme[:'playinginfo.position']) do
52
47
  @win << "[#{Common::to_time(@position)}/#{Common::to_time(@length)}]"
53
48
  end
54
- want_refresh
49
+ @win.refresh
55
50
  end
56
51
 
57
52
  def attach(playlist, player)
58
- with_lock do
59
- player.events.on(:pause) { self.paused! }
60
- player.events.on(:stop) { self.stopped! }
61
- player.events.on(:play) { self.playing! }
62
-
63
- player.events.on(:position_change) do
64
- with_lock do
65
- self.position=(player.position)
66
- self.length=(player.length)
67
- end
68
- end
53
+ player.events.on(:pause) { self.paused! }
54
+ player.events.on(:stop) { self.stopped! }
55
+ player.events.on(:play) { self.playing! }
69
56
 
70
- playlist.events.on(:current_changed) {
71
- self.track=(playlist[playlist.current_playing])
72
- }
57
+ player.events.on(:position_change) do
58
+ self.position=(player.position)
59
+ self.length=(player.length)
60
+ end
73
61
 
74
- # TODO: move mouse?
75
- self.mouse.on(Curses::BUTTON1_CLICKED) do |mevent|
76
- player.toggle
77
- end
62
+ playlist.events.on(:current_changed) {
63
+ self.track=(playlist[playlist.current_playing])
64
+ }
65
+
66
+ # TODO: move mouse?
67
+ self.mouse.on(Curses::BUTTON1_CLICKED) do |mevent|
68
+ player.toggle
78
69
  end
79
70
  end
80
71
 
@@ -98,10 +89,9 @@ module Ektoplayer
98
89
 
99
90
  def draw
100
91
  @win.erase
92
+ draw_position_and_length
101
93
 
102
94
  if @track
103
- draw_position_and_length
104
-
105
95
  fill(Config[:'playinginfo.format1']).each_with_index do |fmt,i|
106
96
  @win.center(fmt[:sum]) if i == 0
107
97
  @win.with_attr(UI::Colors.set(nil, *fmt[:curses_attrs])) do
@@ -110,7 +100,7 @@ module Ektoplayer
110
100
  end
111
101
 
112
102
  @win.with_attr(Theme[:'playinginfo.state']) do
113
- @win.from_right("[#{@state}]".size) << "[#{@state}]"
103
+ @win.from_right(@state.to_s.size + 2) << "[#{@state}]"
114
104
  end
115
105
 
116
106
  @win.next_line
@@ -124,7 +114,7 @@ module Ektoplayer
124
114
  else
125
115
  @win.center_string(STOPPED_HEADING)
126
116
  @win.with_attr(Theme[:'playinginfo.state']) do
127
- @win.from_right('[stopped]'.size) << '[stopped]'
117
+ @win.from_right(9) << '[stopped]'
128
118
  end
129
119
  end
130
120
 
@@ -32,10 +32,9 @@ module Ektoplayer
32
32
 
33
33
  def draw
34
34
  @win.setpos(0,0)
35
-
36
- @progress_width ||= 0
37
- @progress_char ||= Config[:'progressbar.progress_char']
38
- @rest_char ||= Config[:'progressbar.rest_char']
35
+ @progress_width ||= 0
36
+ @progress_char ||= Config[:'progressbar.progress_char']
37
+ @rest_char ||= Config[:'progressbar.rest_char']
39
38
 
40
39
  @win.with_attr(Theme[:'progressbar.progress']) do
41
40
  repeat = (@progress_width - @progress_char.size)
@@ -28,18 +28,20 @@ module Ektoplayer
28
28
  end
29
29
 
30
30
  def level=(level)
31
+ return unless visible?
32
+
31
33
  new_level_width = (1.4 * level * @size.width).to_i.clamp(0, @size.width - 1) rescue 0
32
34
  return if @level_width == new_level_width
33
35
  load_colors
34
36
 
35
37
  if new_level_width > @level_width
36
- @win.setpos(@win.cury, @level_width)
38
+ @win.setpos(0, @level_width)
37
39
  (@level_width).upto(new_level_width).each do |i|
38
40
  @win.attron(@fade[i])
39
41
  @win << @level_char
40
42
  end
41
43
  else
42
- @win.setpos(@win.cury, new_level_width)
44
+ @win.setpos(0, new_level_width)
43
45
  end
44
46
 
45
47
  if (repeat = @size.width - @win.curx - 1) > 0
@@ -48,7 +50,7 @@ module Ektoplayer
48
50
  end
49
51
 
50
52
  @level_width = new_level_width
51
- @win.refresh if visible?
53
+ @win.refresh
52
54
  end
53
55
 
54
56
  def attach(player)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ektoplayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Abendroth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-20 00:00:00.000000000 Z
11
+ date: 2017-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: audite