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,47 @@
1
+ require 'net/http'
2
+ require_relative 'events'
3
+
4
+ module Soundcloud2000
5
+ class DownloadThread
6
+ attr_reader :events, :url, :progress, :total, :file
7
+
8
+ def initialize(url, filename)
9
+ @events = Events.new
10
+ @url = URI.parse(url)
11
+ @file = File.open(filename, "w")
12
+ @progress = 0
13
+ start!
14
+ end
15
+
16
+ def log(s)
17
+ Soundcloud2000::Application.logger.debug("DownloadThread #{s}")
18
+ end
19
+
20
+ def start!
21
+ Thread.start do
22
+ begin
23
+ log :start
24
+ Net::HTTP.get_response(url) do |res|
25
+ log "response: #{res.code}"
26
+ raise res.body if res.code != '200'
27
+
28
+ @total = res.header['Content-Length'].to_i
29
+
30
+ res.read_body do |chunk|
31
+ @progress += chunk.size
32
+ @file << chunk
33
+ @file.close if @progress == @total
34
+ end
35
+ end
36
+ rescue => e
37
+ log e.message
38
+ end
39
+ end
40
+
41
+ sleep 0.1 while @total.nil?
42
+ sleep 0.1
43
+
44
+ self
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module Soundcloud2000
2
+ class Events
3
+ def initialize
4
+ @handlers = Hash.new {|h,k| h[k] = [] }
5
+ end
6
+
7
+ def on(event, &block)
8
+ @handlers[event] << block
9
+ end
10
+
11
+ def trigger(event, *args)
12
+ @handlers[event].each do |handler|
13
+ handler.call(*args)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../events'
2
+
3
+ module Soundcloud2000
4
+ module Models
5
+ class Collection
6
+ include Enumerable
7
+ attr_reader :events, :rows, :page
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ @events = Events.new
12
+ clear
13
+ end
14
+
15
+ def [](*args)
16
+ @rows[*args]
17
+ end
18
+
19
+ def clear
20
+ @page = 0
21
+ @rows = []
22
+ @loaded = false
23
+ end
24
+
25
+ def each(&block)
26
+ @rows.each(&block)
27
+ end
28
+
29
+ def replace(rows)
30
+ clear
31
+ @rows = rows
32
+ events.trigger(:replace)
33
+ end
34
+
35
+ def append(rows)
36
+ @rows += rows
37
+ events.trigger(:append)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,141 @@
1
+ require 'audite'
2
+ require_relative '../download_thread'
3
+
4
+ module Soundcloud2000
5
+ module Models
6
+ class Player
7
+ attr_reader :track, :events
8
+
9
+ def initialize
10
+ @track = nil
11
+ @events = Events.new
12
+ @folder = File.expand_path("~/.soundcloud2000")
13
+ @seek_speed = {}
14
+ @seek_time = {}
15
+ create_player
16
+
17
+ Dir.mkdir(@folder) unless File.exist?(@folder)
18
+ end
19
+
20
+ def create_player
21
+ @player = Audite.new
22
+ @player.events.on(:position_change) do |position|
23
+ events.trigger(:progress)
24
+ end
25
+
26
+ @player.events.on(:complete) do
27
+ events.trigger(:complete)
28
+ end
29
+ end
30
+
31
+ def play(track, location)
32
+ log :play, track.id
33
+ @track = track
34
+ load(track, location)
35
+ start
36
+ end
37
+
38
+ def play_progress
39
+ seconds_played / duration
40
+ end
41
+
42
+ def duration
43
+ @track.duration.to_f / 1000
44
+ end
45
+
46
+ def title
47
+ [@track.title, @track.user.username].join(' - ')
48
+ end
49
+
50
+ def length_in_seconds
51
+ mpg = Mpg123.new(@file)
52
+ mpg.length * mpg.tpf / mpg.spf
53
+ end
54
+
55
+ def load(track, location, &block)
56
+ @file = "#{@folder}/#{track.id}.mp3"
57
+
58
+ if !File.exist?(@file) || track.duration / 1000 < length_in_seconds * 0.95
59
+ File.unlink(@file) rescue nil
60
+ @download = DownloadThread.new(location, @file)
61
+ else
62
+ @download = nil
63
+ end
64
+
65
+ @player.load(@file)
66
+ end
67
+
68
+ def log(*args)
69
+ Soundcloud2000::Application.logger.debug 'Player: ' + args.join(" ")
70
+ end
71
+
72
+ def level
73
+ @player.level
74
+ end
75
+
76
+ def seconds_played
77
+ @player.position
78
+ end
79
+
80
+ def download_progress
81
+ if @download
82
+ @download.progress / @download.total.to_f
83
+ else
84
+ 1
85
+ end
86
+ end
87
+
88
+ def playing?
89
+ @player.active
90
+ end
91
+
92
+ def seek_speed(direction)
93
+ if @seek_time[direction] && Time.now - @seek_time[direction] < 0.5
94
+ @seek_speed[direction] *= 1.05
95
+ else
96
+ @seek_speed[direction] = 1
97
+ end
98
+
99
+ @seek_time[direction] = Time.now
100
+ @seek_speed[direction]
101
+ end
102
+ #change song position
103
+ def seek_position(position)
104
+ position *= 0.1
105
+ relative_position = position * duration
106
+ if relative_position < seconds_played
107
+ difference = seconds_played - relative_position
108
+ @player.rewind(difference)
109
+ elsif download_progress > (relative_position / duration) && relative_position > seconds_played
110
+ log download_progress
111
+ difference = relative_position - seconds_played
112
+ @player.forward(difference)
113
+ end
114
+ end
115
+
116
+ def rewind
117
+ @player.rewind(seek_speed(:rewind))
118
+ end
119
+
120
+ def forward
121
+ seconds = seek_speed(:forward)
122
+
123
+ if ((seconds + seconds_played) / duration) < download_progress
124
+ @player.forward(seconds)
125
+ end
126
+ end
127
+
128
+ def stop
129
+ @player.stop_stream
130
+ end
131
+
132
+ def start
133
+ @player.start_stream
134
+ end
135
+
136
+ def toggle
137
+ @player.toggle
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,23 @@
1
+ module Soundcloud2000
2
+ module Models
3
+ class Playlist
4
+
5
+ def initialize(hash)
6
+ @hash = hash
7
+ end
8
+
9
+ def id
10
+ @hash['id']
11
+ end
12
+
13
+ def title
14
+ @hash['title']
15
+ end
16
+
17
+ def uri
18
+ @hash['uri']
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ require_relative 'user'
2
+
3
+ module Soundcloud2000
4
+ module Models
5
+ class Track
6
+
7
+ def initialize(hash)
8
+ @hash = hash
9
+ end
10
+
11
+ def id
12
+ @hash['id']
13
+ end
14
+
15
+ def title
16
+ @hash['title']
17
+ end
18
+
19
+ def url
20
+ @hash['permalink_url']
21
+ end
22
+
23
+ def user
24
+ @user ||= User.new(@hash['user'])
25
+ end
26
+
27
+ def username
28
+ user.username
29
+ end
30
+
31
+ def duration
32
+ @hash['duration']
33
+ end
34
+
35
+ def length
36
+ TimeHelper.duration(duration)
37
+ end
38
+
39
+ def plays
40
+ @hash['playback_count']
41
+ end
42
+
43
+ def likes
44
+ @hash['favoritings_count']
45
+ end
46
+
47
+ def comments
48
+ @hash['comments']
49
+ end
50
+
51
+ def stream_url
52
+ @hash['stream_url']
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'collection'
2
+ require_relative 'track'
3
+ require_relative 'playlist'
4
+
5
+ module Soundcloud2000
6
+ module Models
7
+ class TrackCollection < Collection
8
+ DEFAULT_LIMIT = 50
9
+
10
+ attr_reader :limit
11
+ attr_accessor :collection_to_load, :user, :playlist
12
+
13
+ def initialize(client)
14
+ super
15
+ @limit = DEFAULT_LIMIT
16
+ @collection_to_load = :recent
17
+ end
18
+
19
+ def size
20
+ @rows.size
21
+ end
22
+
23
+ def clear_and_replace
24
+ clear
25
+ load_more
26
+ events.trigger(:replace)
27
+ end
28
+
29
+ def load
30
+ clear
31
+ load_more
32
+ end
33
+
34
+ def load_more
35
+ unless @loaded
36
+ tracks = self.send(@collection_to_load.to_s + "_tracks")
37
+ @loaded = true if tracks.empty?
38
+ append tracks.map {|hash| Track.new hash }
39
+ @page += 1
40
+ end
41
+ end
42
+
43
+ def favorites_tracks
44
+ return [] if @user.nil?
45
+ @client.get(@user.uri + '/favorites', offset: @limit * @page, limit: @limit)
46
+ end
47
+
48
+ def recent_tracks
49
+ @client.get('/tracks', offset: @page * limit, limit: @limit)
50
+ end
51
+
52
+ def user_tracks
53
+ return [] if @user.nil?
54
+ @client.get(@user.uri + '/tracks', offset: @limit * @page, limit: @limit)
55
+ end
56
+
57
+ def playlist_tracks
58
+ return [] if @playlist.nil?
59
+ @client.get(@playlist.uri + '/tracks', offset: @limit * @page, limit: @limit)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ module Soundcloud2000
2
+ module Models
3
+ class User
4
+
5
+ def initialize(hash)
6
+ @hash = hash
7
+ end
8
+
9
+ def id
10
+ @hash['id']
11
+ end
12
+
13
+ def username
14
+ @hash['username']
15
+ end
16
+
17
+ def uri
18
+ @hash['uri']
19
+ end
20
+
21
+ def permalink
22
+ @hash['permalink']
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require 'time'
2
+
3
+ module Soundcloud2000
4
+ module TimeHelper
5
+ HOUR = 1000 * 60 * 60
6
+ MINUTE = 1000 * 60
7
+ SECONDS = 1000
8
+
9
+ def self.duration(milliseconds)
10
+ parts = [
11
+ milliseconds / 1000 / 60 / 60, # hours
12
+ milliseconds / 1000 / 60 % 60, # minutes
13
+ milliseconds / 1000 % 60, # seconds
14
+ ]
15
+
16
+ parts.shift if parts.first.zero?
17
+
18
+ [ parts.first, *parts[1..-1].map { |part| '%02d' % part } ].join('.')
19
+ end
20
+
21
+ end
22
+ end