soundcloud2000 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +7 -0
  5. data/README.md +55 -0
  6. data/Rakefile +46 -0
  7. data/bin/soundcloud2000 +5 -0
  8. data/lib/soundcloud2000.rb +18 -0
  9. data/lib/soundcloud2000/application.rb +91 -0
  10. data/lib/soundcloud2000/client.rb +51 -0
  11. data/lib/soundcloud2000/controllers/controller.rb +19 -0
  12. data/lib/soundcloud2000/controllers/player_controller.rb +66 -0
  13. data/lib/soundcloud2000/controllers/track_controller.rb +81 -0
  14. data/lib/soundcloud2000/download_thread.rb +47 -0
  15. data/lib/soundcloud2000/events.rb +17 -0
  16. data/lib/soundcloud2000/models/collection.rb +41 -0
  17. data/lib/soundcloud2000/models/player.rb +141 -0
  18. data/lib/soundcloud2000/models/playlist.rb +23 -0
  19. data/lib/soundcloud2000/models/track.rb +57 -0
  20. data/lib/soundcloud2000/models/track_collection.rb +64 -0
  21. data/lib/soundcloud2000/models/user.rb +26 -0
  22. data/lib/soundcloud2000/time_helper.rb +22 -0
  23. data/lib/soundcloud2000/ui/canvas.rb +22 -0
  24. data/lib/soundcloud2000/ui/color.rb +46 -0
  25. data/lib/soundcloud2000/ui/input.rb +48 -0
  26. data/lib/soundcloud2000/ui/rect.rb +14 -0
  27. data/lib/soundcloud2000/ui/table.rb +131 -0
  28. data/lib/soundcloud2000/ui/view.rb +73 -0
  29. data/lib/soundcloud2000/views/player_view.rb +66 -0
  30. data/lib/soundcloud2000/views/splash.rb +69 -0
  31. data/lib/soundcloud2000/views/tracks_table.rb +13 -0
  32. data/soundcloud2000 +8 -0
  33. data/soundcloud2000.gemspec +25 -0
  34. data/spec/controllers/track_controller_spec.rb +59 -0
  35. data/spec/spec_helper.rb +2 -0
  36. metadata +149 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTg4YWEyOTgxYjg2MTVjZGNhNGJkOGQ0ZDc5MDRhMDdlM2Y0YjUyZA==
5
+ data.tar.gz: !binary |-
6
+ ZmZiYTAwMzUxZDM5MzRmMjMwOTRiYmI4OWFkYjgxZWUxMjJlOThiMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjcxZTM2NGFiMGRmZDM5M2ZlNTk5ZWNiNjFmODhmZjg5OTA0NGFiNjM1NGI3
10
+ OGI0NzI2ZDBmMDMyMzgzZGRhNTZhNmZjMmFmZTk4M2Y2YWVlZDJmN2Y2YmQ1
11
+ Y2Q4MmJkZGNmY2Q3NWI3MzdhOTlhZjc0MzRjMmYzNjRiZGI3YzA=
12
+ data.tar.gz: !binary |-
13
+ ODQzOTBhZGFhNDlmYTU4MDY5N2EyOTY3NGQ1ZTc1MmUzZmRiZDQ1NmRlOTlj
14
+ ZjM5M2I4MzQxNDUwMjhmYWRlZmYwYzM3NjZiYWViZWZhMDdiMmVkYjIxMjZk
15
+ OTk0YmZkNzVlZmNlNjFmOWZmMzg0NGIwYTJiZTljMGU5MGVhZmI=
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ *~
3
+ *.log
4
+ Gemfile.lock
5
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in audite.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013 Matthias Georgi and Tobias Schmidt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ # soundcloud2000
2
+
3
+ The next generation SoundCloud client. Without all these stupid CSS files. Runs on OSX and Linux.
4
+
5
+ ![Screen Shot 2013-01-20 at 15 37 03](https://f.cloud.github.com/assets/3432/81282/06c44c7e-630f-11e2-9a91-85c9b917835c.png)
6
+ ![Screen Shot 2013-01-20 at 15 37 54](https://f.cloud.github.com/assets/3432/81281/06b05df4-630f-11e2-8b55-7f3c18126831.png)
7
+
8
+ This hack was built at the [Music Hack Day Stockholm 2013](http://stockholm.musichackday.org/2013).
9
+
10
+ ## Requirements
11
+
12
+ * Ruby (1.9)
13
+ * Portaudio (19)
14
+ * Mpg123 (1.14)
15
+
16
+ ## Installation
17
+
18
+ Assuming you have Ruby/Rubygems/Bundler installed, you need
19
+ portaudio and mpg123 as library to compile the native extensions.
20
+
21
+ ### OSX
22
+
23
+ brew install portaudio
24
+ brew install mpg123
25
+ bundle install
26
+ bin/soundcloud2000
27
+
28
+ ### Debian / Ubuntu
29
+
30
+ apt-get install portaudio19-dev libmpg123-dev
31
+ bundle install
32
+ bin/soundcloud2000
33
+
34
+ ## Features
35
+
36
+ * stream SoundCloud tracks in your terminal (`enter`)
37
+ * scroll through sound lists (`down` / `up`)
38
+ * play / pause support (`space`)
39
+ * forward / rewind support (`right` / `left`)
40
+ * play tracks of different users (`u`)
41
+ * level meter
42
+
43
+ ## Planned
44
+
45
+ * play any streams, sets or sounds
46
+ * better browsing between users and sound lists
47
+
48
+ ## Authors
49
+
50
+ * [Matthias Georgi](https://github.com/georgi) ([@mgeorgi](https://twitter.com/mgeorgi))
51
+ * [Tobias Schmidt](https://github.com/grobie) ([@dagrobie](https://twitter.com/dagrobie))
52
+
53
+ ## Contributors
54
+
55
+ * [Travis Thieman](https://github.com/tthieman) ([@tthieman](https://twitter.com/thieman))
@@ -0,0 +1,46 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'yaml'
4
+
5
+ def dependencies(file)
6
+ `otool -L #{file}`.split("\n")[1..-1].map {|line| line.split[0] }
7
+ end
8
+
9
+ task :dirs do
10
+ Dir.mkdir 'vendor' rescue nil
11
+ Dir.mkdir 'vendor/bin' rescue nil
12
+ Dir.mkdir 'vendor/lib' rescue nil
13
+ Dir.mkdir 'vendor/dyld' rescue nil
14
+ end
15
+
16
+ task :ruby => :dirs do
17
+ lines = `rvm info`.split("\n")[6..-1]
18
+ ruby = YAML.load(lines.join("\n"))
19
+
20
+ version = ruby.keys.first
21
+ home = ruby[version]['homes']['ruby']
22
+ bin = ruby[version]['binaries']['ruby']
23
+ # dylib = dependencies(bin)[0]
24
+
25
+ `cp -a #{home}/lib/ruby/1.9.1/* vendor/lib`
26
+ `cp #{bin} vendor/`
27
+ # `cp #{dylib} vendor/dyld`
28
+ end
29
+
30
+ task :libs => :dirs do
31
+ [:audite, :portaudio, :mpg123].each do |name|
32
+ lib = `gem which #{name}`.chomp
33
+ `cp #{lib} vendor/lib`
34
+ if File.extname(lib) == '.bundle'
35
+ dylib = dependencies(lib)[0]
36
+ `cp #{dylib} vendor/dyld`
37
+ end
38
+ end
39
+ end
40
+
41
+ task :package => [:ruby, :libs] do
42
+ `cp soundcloud2000 vendor`
43
+ `cp -a bin/* vendor/bin`
44
+ `cp -a lib/* vendor/lib`
45
+ `tar czf soundcloud2000.tgz vendor`
46
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'soundcloud2000'
4
+
5
+ Soundcloud2000.start
@@ -0,0 +1,18 @@
1
+ require_relative 'soundcloud2000/client'
2
+ require_relative 'soundcloud2000/application'
3
+
4
+ module Soundcloud2000
5
+ CLIENT_ID = '29f8e018e1272c27bff7d510a10da2a8'
6
+
7
+ def self.start
8
+ client = Client.new(CLIENT_ID)
9
+ application = Application.new(client)
10
+
11
+ Signal.trap('SIGINT') do
12
+ application.stop
13
+ end
14
+
15
+ application.run
16
+ end
17
+
18
+ end
@@ -0,0 +1,91 @@
1
+ require 'logger'
2
+
3
+ require_relative 'ui/canvas'
4
+ require_relative 'ui/input'
5
+ require_relative 'ui/rect'
6
+
7
+ require_relative 'controllers/track_controller'
8
+ require_relative 'controllers/player_controller'
9
+
10
+ require_relative 'models/track_collection'
11
+ require_relative 'models/player'
12
+
13
+ require_relative 'views/tracks_table'
14
+ require_relative 'views/splash'
15
+
16
+ module Soundcloud2000
17
+ class Application
18
+ include Controllers
19
+ include Models
20
+ include Views
21
+
22
+ def initialize(client)
23
+ $stderr.reopen("debug.log", "w")
24
+ @canvas = UI::Canvas.new
25
+
26
+ @splash_controller = Controller.new(Splash.new(
27
+ UI::Rect.new(0, 0, Curses.cols, Curses.lines)))
28
+
29
+ @player_controller = PlayerController.new(PlayerView.new(
30
+ UI::Rect.new(0, 0, Curses.cols, 5)), client)
31
+
32
+ @track_controller = TrackController.new(TracksTable.new(
33
+ UI::Rect.new(0, 5, Curses.cols, Curses.lines - 5)), client)
34
+
35
+ @track_controller.bind_to(TrackCollection.new(client))
36
+
37
+ @track_controller.events.on(:select) do |track|
38
+ @player_controller.play(track)
39
+ end
40
+
41
+ @player_controller.events.on(:complete) do
42
+ @track_controller.next_track
43
+ end
44
+ end
45
+
46
+ def main
47
+ loop do
48
+ if @workaround_was_called_once_already
49
+ handle UI::Input.get(-1)
50
+ else
51
+ @workaround_was_called_once_already = true
52
+ handle UI::Input.get(0)
53
+ @track_controller.load
54
+ @track_controller.render
55
+ end
56
+
57
+ break if stop?
58
+ end
59
+ ensure
60
+ @canvas.close
61
+ end
62
+
63
+ def run
64
+ @splash_controller.render
65
+ main
66
+ end
67
+
68
+ # TODO: look at active controller and send key to active controller instead
69
+ def handle(key)
70
+ case key
71
+ when :left, :right, :space, :one, :two, :three, :four, :five, :six, :seven, :eight, :nine
72
+ @player_controller.events.trigger(:key, key)
73
+ when :down, :up, :enter, :u, :f, :s, :j, :k
74
+ @track_controller.events.trigger(:key, key)
75
+ end
76
+ end
77
+
78
+ def stop
79
+ @stop = true
80
+ end
81
+
82
+ def stop?
83
+ @stop == true
84
+ end
85
+
86
+ def self.logger
87
+ @logger ||= Logger.new('debug.log')
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,51 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Soundcloud2000
5
+ class Client
6
+ DEFAULT_LIMIT = 50
7
+
8
+ attr_reader :client_id
9
+
10
+ def initialize(client_id)
11
+ @client_id = client_id
12
+ end
13
+
14
+ def tracks(page = 1, limit = DEFAULT_LIMIT)
15
+ get('/tracks', offset: (page - 1) * limit, limit: limit)
16
+ end
17
+
18
+ def resolve(permalink)
19
+ res = get('/resolve', url: "http://soundcloud.com/#{permalink}")
20
+ if location = res['location']
21
+ get URI.parse(location).path
22
+ end
23
+ end
24
+
25
+ def uri_escape(params)
26
+ URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))
27
+ end
28
+
29
+ def request(type, path, params={})
30
+ params[:client_id] = client_id
31
+ params[:format] = 'json'
32
+
33
+ Net::HTTP.start('api.soundcloud.com', 443, :use_ssl => true) do |http|
34
+ http.request(type.new("#{path}?#{uri_escape params}"))
35
+ end
36
+ end
37
+
38
+ def get(path, params={})
39
+ JSON.parse(request(Net::HTTP::Get, path, params).body)
40
+ end
41
+
42
+ def location(url)
43
+ uri = URI.parse(url)
44
+ res = request(Net::HTTP::Get, uri.path)
45
+ if res.code == '302'
46
+ res.header['Location']
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ require_relative '../events'
2
+
3
+ module Soundcloud2000
4
+ module Controllers
5
+ class Controller
6
+ attr_reader :events
7
+
8
+ def initialize(view)
9
+ @view = view
10
+ @events = Events.new
11
+ end
12
+
13
+ def render
14
+ @view.render
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,66 @@
1
+ require_relative 'controller'
2
+ require_relative '../models/player'
3
+ require_relative '../views/player_view'
4
+
5
+ module Soundcloud2000
6
+ module Controllers
7
+ class PlayerController < Controller
8
+
9
+ def initialize(view, client)
10
+ super(view)
11
+
12
+ @client = client
13
+ @player = Models::Player.new
14
+
15
+ @player.events.on(:progress) do
16
+ @view.render
17
+ end
18
+
19
+ @player.events.on(:complete) do
20
+ events.trigger(:complete)
21
+ end
22
+
23
+ @view.player(@player)
24
+
25
+ events.on(:key) do |key|
26
+ case key
27
+ when :left
28
+ @player.rewind
29
+ when :right
30
+ @player.forward
31
+ when :space
32
+ if @player.track
33
+ @player.toggle
34
+ @view.render
35
+ end
36
+ when :one
37
+ @player.seek_position(1)
38
+ when :two
39
+ @player.seek_position(2)
40
+ when :three
41
+ @player.seek_position(3)
42
+ when :four
43
+ @player.seek_position(4)
44
+ when :five
45
+ @player.seek_position(5)
46
+ when :six
47
+ @player.seek_position(6)
48
+ when :seven
49
+ @player.seek_position(7)
50
+ when :eight
51
+ @player.seek_position(8)
52
+ when :nine
53
+ @player.seek_position(9)
54
+ end
55
+ end
56
+ end
57
+
58
+ def play(track)
59
+ location = @client.location(track.stream_url)
60
+
61
+ @player.play(track, location)
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,81 @@
1
+ require_relative 'controller'
2
+ require_relative '../time_helper'
3
+ require_relative '../ui/table'
4
+ require_relative '../ui/input'
5
+ require_relative '../models/track_collection'
6
+ require_relative '../models/user'
7
+
8
+ module Soundcloud2000
9
+ module Controllers
10
+ class TrackController < Controller
11
+
12
+ def initialize(view, client)
13
+ super(view)
14
+
15
+ @client = client
16
+
17
+ events.on(:key) do |key|
18
+ case key
19
+ when :enter
20
+ @view.select
21
+ events.trigger(:select, current_track)
22
+ when :up, :k
23
+ @view.up
24
+ when :down, :j
25
+ @view.down
26
+ @tracks.load_more if @view.bottom?
27
+ when :u
28
+ @tracks.user = fetch_user_with_message('Change to SoundCloud user: ')
29
+ @tracks.collection_to_load = :user
30
+ @tracks.clear_and_replace
31
+ when :f
32
+ if @tracks.user.nil?
33
+ @tracks.user = fetch_user_with_message('Change to favorites from SoundCloud user: ')
34
+ end
35
+ @tracks.collection_to_load = :favorites
36
+ @tracks.clear_and_replace
37
+ when :s
38
+ @view.clear
39
+ if @tracks.user.nil?
40
+ @tracks.user = fetch_user_with_message('Change to SoundCloud user: ')
41
+ end
42
+ set = UI::Input.getstr('Change to SoundCloud playlist: ')
43
+ @tracks.playlist = Models::Playlist.new(@client.resolve(@tracks.user.permalink + '/sets/' + set))
44
+ @tracks.collection_to_load = :playlist
45
+ @tracks.clear_and_replace
46
+ end
47
+ end
48
+ end
49
+
50
+ def fetch_user_with_message(message_to_display)
51
+ permalink = UI::Input.getstr(message_to_display)
52
+ user_hash = @client.resolve(permalink)
53
+ if user_hash
54
+ Models::User.new(user_hash)
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def current_track
61
+ @tracks[@view.current]
62
+ end
63
+
64
+ def bind_to(tracks)
65
+ @tracks = tracks
66
+ @view.bind_to(tracks)
67
+ end
68
+
69
+ def load
70
+ @tracks.load
71
+ end
72
+
73
+ def next_track
74
+ @view.down
75
+ @view.select
76
+ events.trigger(:select, current_track)
77
+ end
78
+
79
+ end
80
+ end
81
+ end