cloudruby 1.0.4 → 1.1.0

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