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