cloudruby 1.0.4 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c190dbded5261f41bb498514cc555dc242ce2ac6
4
- data.tar.gz: e4e64db8837ca1db44c143b92064b4e38fb845b6
3
+ metadata.gz: 7d65284ff7faa2d2be8f6b1ef10fb0ed825557fb
4
+ data.tar.gz: 500dbcb28787f33896a1493abcff9bc96b49c1a1
5
5
  SHA512:
6
- metadata.gz: 016cb876daaa4694fea7289d10779e86d4ebe1efb0c35338aa4b9bd0f35bc5e687c23fe3968bed5c04e25eb2fd5396ab26059d995c16d3b2d883011e3148d58c
7
- data.tar.gz: a53de0174fbfb83dd03971fff46e2e17c8cd185b230bed7085faf7eadcf63e8e1b86e4299d68e47abfc688a172d10200f50617988846baf5024705ca8db33b6c
6
+ metadata.gz: 8f9753df56f183867581be5cd7b601c525a23476684e040ffa593f6f57163d50bf1a6e31acf333712503fa22f4c0c3f86ebb883f182742f9531ee90ae8b9cce8
7
+ data.tar.gz: 973cde30b03d01ef0922fcf41fad6f76330971522eaf39f512c2d0dac5ab98cf1fc2161944057b147a47ea6c721430ca2e89efae37ce92f11d740fdf76b5ec8d
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json/pure'
4
4
  require 'cloudruby'
5
+ # require_relative '../lib/cloudruby'
5
6
 
6
7
  @config = {}
7
8
  @query = []
@@ -4,22 +4,27 @@ require 'logger'
4
4
  require 'json/pure'
5
5
  require_relative 'soundcloud.rb'
6
6
  require_relative 'mpg123player.rb'
7
+ require_relative 'gstplayer.rb'
7
8
  require_relative 'ncurses_ui.rb'
8
9
 
9
10
  class Cloudruby
10
11
  def init(q, config)
11
12
  @config = config
12
- @cloud = SoundCloud.new "76796f79392f9398288cdac3fe3391c0"
13
- @player = MPG123Player.new
14
- @ui = NCursesUI.new self, (@config[:ncurses] || @config[:curses])
15
13
 
16
14
  # @logger = Logger.new "logfile.log"
17
15
  @logger = Logger.new STDERR
18
16
  # @logger.level = Logger::DEBUG
19
17
  @logger.level = Logger::Severity::UNKNOWN
20
18
 
21
- @player.logger = @logger
22
- @ui.logger = @logger
19
+ @cloud = SoundCloud.new "76796f79392f9398288cdac3fe3391c0", logger: @logger
20
+ case @config[:"audio-backend"]
21
+ when "gstreamer"
22
+ @player = GstPlayer.new logger: @logger, audio_params: @config[:"audio-params"]
23
+ else
24
+ @player = MPG123Player.new logger: @logger
25
+ end
26
+ @ui = NCursesUI.new self, (@config[:ncurses] || @config[:curses]), logger: @logger, audio_backend: @player
27
+
23
28
  @logger.info {"logger inited"}
24
29
 
25
30
  @player.add_observer @ui, :player_update
@@ -0,0 +1,209 @@
1
+ require 'gst'
2
+
3
+ class GstPlayer
4
+ include Observable
5
+ attr_reader :error, :paused
6
+ attr_accessor :logger, :audio_params
7
+
8
+ def version
9
+ as = nil
10
+ unless @pipeline.nil?
11
+ as = @pipeline.get_property "audio-sink"
12
+ as = ", Sink: #{as.name}"
13
+ end
14
+ return "Audio backend: Gstreamer #{Gst.version.join('.')}#{as}"
15
+ end
16
+
17
+ def initialize params = {}
18
+ params.each { |key, value| send "#{key}=", value }
19
+
20
+ @logger.info version
21
+ @inqueue = []
22
+ begin
23
+ @pipeline = Gst::ElementFactory.make("playbin", "cloudruby_pipeline")
24
+ if @pipeline.nil?
25
+ puts "Cannot initialize playing pipeline. Is gstreamer installed?"
26
+ exit false
27
+ end
28
+ @pipeline.set_property "buffer-size", 16 * 1024 * 1024
29
+ if @audio_params
30
+ @audio_params.each do |key, value|
31
+ case key
32
+ when :"audio-sink"
33
+ sink = Gst::ElementFactory.make(value, "#{value}")
34
+ @pipeline.set_property "audio-sink", sink unless sink.nil?
35
+ when :"buffer-duration", :"buffer-size", :"mute", :"volume"
36
+ @pipeline.set_property key, value
37
+ end
38
+ end
39
+ end
40
+
41
+ changed
42
+ notify_observers :state => :inited
43
+ runMainLoop
44
+ rescue => err
45
+ @error = err
46
+ changed
47
+ notify_observers :state => :error, :error => err
48
+ end
49
+ end
50
+
51
+ def play(track)
52
+ @last_track = track
53
+ unless track.nil? || track.is_a?(Hash) && track[:error] && !Gst.valid_uri?(track["mpg123url"])
54
+ gstPlayUrl track["mpg123url"]
55
+ end
56
+ changed
57
+ notify_observers :state => :load, :track => track
58
+ rescue => err
59
+ @error = err
60
+ changed
61
+ notify_observers :state => :error, :error => err
62
+ end
63
+
64
+ def playing?
65
+ @pipeline.playing?
66
+ end
67
+
68
+ def pause()
69
+ @paused = !@paused
70
+ if @paused
71
+ @pipeline.pause
72
+ else
73
+ @pipeline.play
74
+ end
75
+ rescue => err
76
+ @error = err
77
+ changed
78
+ notify_observers :state => :error, :error => err
79
+ end
80
+
81
+ def stop()
82
+ @playbin.stop
83
+ rescue => err
84
+ @error = err
85
+ changed
86
+ notify_observers :state => :error, :error => err
87
+ end
88
+
89
+ def volume= (val)
90
+ @volume = volume
91
+ @volume += val
92
+ @volume = [@volume, 100].min
93
+ @volume = [@volume, 0].max
94
+ @pipeline.volume = @volume/100.0
95
+ changed
96
+ notify_observers :state => :status, :type => "Volume", :value => "#{@volume}%"
97
+ rescue => err
98
+ @error = err
99
+ changed
100
+ notify_observers :state => :error, :error => err
101
+ end
102
+
103
+ def volume
104
+ (@pipeline.get_property("volume") * 100).to_int
105
+ end
106
+
107
+ def mute
108
+ @pipeline.set_property "mute", !@pipeline.get_property("mute")
109
+ changed
110
+ notify_observers :state => :status, :type => "Volume", :value => "#{muted? ? 0 : @volume}%"
111
+ rescue => err
112
+ @error = err
113
+ changed
114
+ notify_observers :state => :error, :error => err
115
+ end
116
+
117
+ def muted?
118
+ @pipeline.get_property "mute"
119
+ end
120
+
121
+ def close()
122
+ @pipeline.stop
123
+ @mainloop.quit
124
+ changed
125
+ notify_observers :state => :closed
126
+ rescue => err
127
+ @error = err
128
+ changed
129
+ notify_observers :state => :error, :error => err
130
+ end
131
+
132
+ def gstPlayUrl(url)
133
+ @pipeline.stop
134
+ @pipeline.uri = url
135
+ @pipeline.play
136
+ end
137
+
138
+ private
139
+
140
+ def runMainLoop
141
+ @logger.debug {"listen to gst pipeline"}
142
+ @mainloop = GLib::MainLoop.new nil, true
143
+
144
+ # Add custom tasks
145
+ GLib::Timeout.add_seconds(1) do
146
+ pos_res, position = @pipeline.query_position(Gst::Format::TIME)
147
+ dur_res, duration = @pipeline.query_duration(Gst::Format::TIME)
148
+ # @pipeline.query
149
+ duration = (duration / 1000000.0 / 1000).to_int
150
+ position = (position / 1000000.0 / 1000).to_int
151
+ if position > 0 && duration > 0 && duration > position
152
+ changed
153
+ notify_observers :state => :info,
154
+ :frame => position,
155
+ :frameleft => duration-position,
156
+ :time => position,
157
+ :timeleft => position-duration
158
+ end
159
+ true
160
+ end
161
+
162
+ bus = @pipeline.bus
163
+ bus.add_watch do |bus, message|
164
+ raise "message nil" if message.nil?
165
+
166
+ case message.type
167
+ when Gst::MessageType::EOS
168
+ changed
169
+ notify_observers :state => :stop
170
+ when Gst::MessageType::WARNING
171
+ warning, debug = message.parse_warning
172
+ changed
173
+ notify_observers :state => :error, :error => warning.message
174
+ when Gst::MessageType::ERROR
175
+ error, debug = message.parse_error
176
+ changed
177
+ notify_observers :state => :error, :error => error.message
178
+ when Gst::MessageType::BUFFERING
179
+ percent = message.parse_buffering
180
+ changed
181
+ notify_observers :state => :status, :type => "Buffering", :value => "#{percent}%"
182
+ when Gst::MessageType::STATE_CHANGED
183
+ error, state = message.parse_state_changed
184
+ case state
185
+ when Gst::State::PAUSED
186
+ changed
187
+ notify_observers :state => :pause
188
+ when Gst::State::PLAYING
189
+ changed
190
+ notify_observers :state => :play
191
+ end
192
+ else
193
+ @logger.info {"bus event: #{message.type.name} #{message.type} #{message.structure}"}
194
+ end
195
+ true
196
+ end
197
+
198
+ # @pipeline.play
199
+ context = @mainloop.context
200
+ thread = Thread.new do
201
+ while @mainloop.running? do
202
+ context.iteration false
203
+ sleep 0.05
204
+ end
205
+ end
206
+
207
+ end
208
+
209
+ end
@@ -1,13 +1,28 @@
1
1
  require 'open3'
2
+ begin
3
+ require 'httpclient'
4
+ rescue LoadError
5
+ end
2
6
 
3
7
  class MPG123Player
4
8
  include Observable
5
9
  attr_reader :error, :paused
6
10
  attr_accessor :logger
7
11
 
8
- def initialize()
12
+ def version
13
+ return "Audio backend: #{`mpg123 --version`.strip}"
14
+ end
15
+
16
+ def initialize params = {}
17
+ params.each { |key, value| send "#{key}=", value }
18
+ unless defined? HTTPClient
19
+ puts "Mpg123 requires HTTPClient to work"
20
+ exit false
21
+ end
22
+
9
23
  @volume = 100
10
24
  @muted = false
25
+ @logger.info version
11
26
  @inqueue = []
12
27
  begin
13
28
  @pin, @pout, @perr = Open3.popen3 "mpg123 --keep-open --remote"
@@ -25,7 +40,11 @@ class MPG123Player
25
40
  def play(track)
26
41
  @last_track = track
27
42
  unless track.nil? || track.is_a?(Hash) && track[:error]
28
- mpg123puts "load #{track["mpg123url"]}"
43
+ # mpg123puts "load #{track["mpg123url"]}"
44
+ strhead = HTTPClient.new.head(track['mpg123url'])
45
+ streamuri = strhead.header['Location'][0]
46
+ streamuri.sub! 'https', 'http'
47
+ mpg123puts "load #{streamuri}"
29
48
  @pstate = 2
30
49
  end
31
50
  changed
@@ -1,9 +1,25 @@
1
1
  require 'curses'
2
2
 
3
+ class ::Hash
4
+ def deep_merge(second)
5
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
6
+ self.merge(second, &merger)
7
+ end
8
+ end
9
+
3
10
  class NCursesUI
4
- attr_accessor :logger
11
+ attr_accessor :logger, :audio_backend
12
+
13
+ def initialize cloud, options = {}, params = {}
14
+ params.each { |key, value| send "#{key}=", value }
15
+
16
+ if not @audio_backend
17
+ @audio_backend = Object.new
18
+ def @audio_backend.version
19
+ "Audio backend: None"
20
+ end
21
+ end
5
22
 
6
- def initialize cloud, options = {}
7
23
  defaults = {
8
24
  :colors => {
9
25
  :default => [:cyan, :blue],
@@ -12,11 +28,12 @@ class NCursesUI
12
28
  :progress => [:cyan, :blue],
13
29
  :progress_bar => [:blue, :cyan],
14
30
  :title => [:cyan, :black],
15
- :artist => [:cyan, :black]
31
+ :artist => [:cyan, :black],
32
+ :status => [:magenta, :black]
16
33
  }
17
34
  }.freeze
18
35
 
19
- @options = defaults.merge(options || {})
36
+ @options = defaults.deep_merge(options || {})
20
37
  @cloud = cloud
21
38
  @state = :running
22
39
  @frac = 0
@@ -40,7 +57,7 @@ class NCursesUI
40
57
  Curses.timeout = 5
41
58
  @p = NProgress.new stdscr, 0, 0, :progress, :progress_bar
42
59
  @l = NPlaylist.new stdscr, 4, 0, :playlist, :playlist_active, 0, 0, @playlist
43
- @i = NInfobox.new stdscr, 4, 0, :playlist, 0, 8
60
+ @i = NInfobox.new self, stdscr, 4, 0, :playlist, 0, 9
44
61
  @d = NDownloadBox.new stdscr, Curses.lines-1, 0, :default, 0, 1
45
62
  @l.active = 0
46
63
  last_ch = nil
@@ -78,8 +95,10 @@ class NCursesUI
78
95
  @cloud.pause
79
96
  end
80
97
 
81
- if @error
82
- Nutils.print stdscr, 3, 0, "Error: #{@error}", :red
98
+ statusLine = @status || @error
99
+
100
+ if statusLine
101
+ Nutils.print stdscr, 3, 0, "#{statusLine}", :status
83
102
  Curses.refresh
84
103
  end
85
104
  tr = " %s " % [Nutils.timestr(@timetotal)]
@@ -122,7 +141,7 @@ class NCursesUI
122
141
  @l.active = pos if @l
123
142
  when :download
124
143
  if arg[:error]
125
- @error = arg[:error]
144
+ @error = "Error: #{arg[:error]}"
126
145
  end
127
146
  if arg[:count]
128
147
  count = arg[:count]
@@ -146,13 +165,13 @@ class NCursesUI
146
165
  when :load
147
166
  track = arg[:track]
148
167
  if track.nil?
149
- @error = "Nothing found!"
168
+ @error = "Error: Nothing found!"
150
169
  else
151
170
  @error = nil
152
171
  @title = track["title"]
153
172
  @username = track["user"]["username"]
154
173
  @timetotal = track["duration"]
155
- @error = track[:error] if track[:error]
174
+ @error = "Error: #{track[:error]}" if track[:error]
156
175
  end
157
176
  when :info
158
177
  frame = arg[:frame].to_f
@@ -166,7 +185,19 @@ class NCursesUI
166
185
  when :stop
167
186
  @op = "\u25FC"
168
187
  when :error
169
- @error = arg[:error]
188
+ @error = "Error: #{arg[:error]}"
189
+ when :status
190
+ if arg[:type]
191
+ @status = "#{arg[:type]}: #{arg[:value]}"
192
+ if @statusTimeout
193
+ @statusTimeout.exit
194
+ end
195
+ @statusTimeout = Thread.new do
196
+ sleep 5
197
+ @status = nil
198
+ @statusTimeout = nil
199
+ end
200
+ end
170
201
  end
171
202
  end
172
203
 
@@ -382,7 +413,8 @@ end
382
413
 
383
414
  class NInfobox
384
415
  attr_accessor :visible
385
- def initialize scr, row, col, color, w, h
416
+ def initialize parent, scr, row, col, color, w, h
417
+ @parent = parent
386
418
  @scr = scr
387
419
  @row = row
388
420
  @col = col
@@ -418,12 +450,13 @@ class NInfobox
418
450
 
419
451
  def refresh
420
452
  return unless @visible
421
- Nutils.print @win, 1, 2, "Cloudruby v1", :default
422
- Nutils.print @win, 2, 4, "Curses version: #{(Curses.const_defined?"VERSION")?Curses::VERSION : "N/A"}", :default
423
- Nutils.print @win, 3, 4, "Ruby version: #{RUBY_VERSION}", :default
424
- Nutils.print @win, 4, 4, "Author: kulpae <my.shando@gmail.com>", :artist
425
- Nutils.print @win, 5, 4, "Website: uraniumlane.net", :title
426
- Nutils.print @win, 6, 4, "License: MIT", :default
453
+ Nutils.print @win, 1, 2, "Cloudruby v1.1", :default
454
+ Nutils.print @win, 2, 4, "UI Toolkit: #{(Curses.const_defined?"VERSION")?Curses::VERSION : "N/A"}", :default
455
+ Nutils.print @win, 3, 4, "#{@parent.audio_backend.version}", :default
456
+ Nutils.print @win, 4, 4, "Ruby version: #{RUBY_VERSION}", :default
457
+ Nutils.print @win, 5, 4, "Author: kulpae <my.shando@gmail.com>", :artist
458
+ Nutils.print @win, 6, 4, "Website: uraniumlane.net", :title
459
+ Nutils.print @win, 7, 4, "License: MIT", :default
427
460
  @win.attron(Colors.map(@color)) if @color
428
461
  @win.box 0, 0
429
462
  @win.attroff(Colors.map(@color)) if @color
@@ -5,8 +5,10 @@ require 'json/pure'
5
5
  class SoundCloud
6
6
  include Observable
7
7
  LIMIT = 100
8
+ attr_accessor :logger
8
9
 
9
- def initialize(client_id)
10
+ def initialize client_id, params = {}
11
+ params.each { |key, value| send "#{key}=", value }
10
12
  @cid = client_id
11
13
  @playlist_pos = -1
12
14
  @download_queue = []
@@ -16,9 +18,9 @@ class SoundCloud
16
18
  def load_playlist(search = nil, offset = 0)
17
19
  search = "" unless search && !search.empty?
18
20
  if search =~ /\s*http(s)?:\/\/(www.)?soundcloud.com.*/
19
- url = "http://api.soundcloud.com/resolve.json?url=%s&client_id=%s" % [CGI.escape(search), @cid]
21
+ url = "https://api.soundcloud.com/resolve.json?url=%s&client_id=%s" % [CGI.escape(search), @cid]
20
22
  else
21
- url = "http://api.soundcloud.com/tracks.json?client_id=%s&filter=streamable&limit=%d&offset=%d&q=%s" \
23
+ url = "https://api.soundcloud.com/tracks.json?client_id=%s&filter=streamable&limit=%d&offset=%d&q=%s" \
22
24
  % [@cid, LIMIT, offset, CGI.escape(search)]
23
25
  end
24
26
  c = open(url) do |io|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Koch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-21 00:00:00.000000000 Z
11
+ date: 2015-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httpclient
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
41
55
  description: "== A soundcloud player written in Ruby \nwith Ncurses for user interface
42
56
  and \nmpg123 for playback.\n\nRequires the mpg123 executable to be present in the
43
57
  PATH.\n\n== Usage\n\n* lists recently uploaded tracks from soundcloud\n cloudruby\n\n*
@@ -51,6 +65,7 @@ extra_rdoc_files: []
51
65
  files:
52
66
  - bin/cloudruby
53
67
  - lib/cloudruby.rb
68
+ - lib/gstplayer.rb
54
69
  - lib/mpg123player.rb
55
70
  - lib/ncurses_ui.rb
56
71
  - lib/soundcloud.rb
@@ -74,9 +89,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
89
  - !ruby/object:Gem::Version
75
90
  version: '0'
76
91
  requirements:
92
+ - gem gstreamer (optional)
77
93
  - mpg123 executable
78
94
  rubyforge_project:
79
- rubygems_version: 2.2.2
95
+ rubygems_version: 2.4.8
80
96
  signing_key:
81
97
  specification_version: 4
82
98
  summary: Ncurses player for Soundcloud tracks in Ruby