ektoplayer 0.1.3 → 0.1.5

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.
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