radiodan 0.0.4 → 1.0.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 +7 -0
- data/Gemfile.lock +28 -3
- data/TODO +7 -4
- data/lib/radiodan/adapter/mpd/ack.rb +3 -3
- data/lib/radiodan/adapter/mpd/connection.rb +6 -17
- data/lib/radiodan/adapter/mpd/playlist_parser.rb +15 -8
- data/lib/radiodan/adapter/mpd/response.rb +25 -21
- data/lib/radiodan/adapter/mpd.rb +96 -11
- data/lib/radiodan/builder.rb +11 -10
- data/lib/radiodan/em_additions.rb +7 -5
- data/lib/radiodan/event_binding.rb +4 -1
- data/lib/radiodan/logging.rb +18 -7
- data/lib/radiodan/middleware/panic.rb +6 -12
- data/lib/radiodan/middleware/playlist_to_start.rb +11 -0
- data/lib/radiodan/middleware/toggle_playlist.rb +19 -0
- data/lib/radiodan/middleware/touch_file.rb +1 -1
- data/lib/radiodan/middleware/web_server.rb +17 -0
- data/lib/radiodan/player.rb +52 -25
- data/lib/radiodan/playlist.rb +25 -4
- data/lib/radiodan/playlist_sync.rb +39 -15
- data/lib/radiodan/sinatra.rb +13 -0
- data/lib/radiodan/track.rb +6 -3
- data/lib/radiodan/version.rb +1 -1
- data/lib/radiodan.rb +12 -12
- data/radiodan.gemspec +6 -3
- data/spec/lib/builder_spec.rb +24 -3
- data/spec/lib/logging_spec.rb +7 -2
- data/spec/lib/player_spec.rb +10 -6
- data/spec/lib/playlist_spec.rb +47 -5
- data/spec/lib/playlist_sync_spec.rb +49 -19
- data/spec/spec_helper.rb +1 -1
- metadata +53 -35
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'thin'
|
2
|
+
|
3
|
+
class Radiodan
|
4
|
+
class WebServer
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
def initialize(*config)
|
8
|
+
@klass = config.shift
|
9
|
+
@options = config.shift || {}
|
10
|
+
@port = @options.fetch(:port, 3000)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(player)
|
14
|
+
Thin::Server.start @klass.new(player), '0.0.0.0', @port, :signals => false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/radiodan/player.rb
CHANGED
@@ -10,8 +10,7 @@ class Player
|
|
10
10
|
include EventBinding
|
11
11
|
|
12
12
|
attr_reader :adapter, :playlist
|
13
|
-
|
14
|
-
|
13
|
+
|
15
14
|
def adapter=(adapter)
|
16
15
|
@adapter = adapter
|
17
16
|
@adapter.player = self
|
@@ -23,17 +22,22 @@ class Player
|
|
23
22
|
|
24
23
|
def playlist=(new_playlist)
|
25
24
|
@playlist = new_playlist
|
26
|
-
|
27
|
-
|
25
|
+
|
26
|
+
trigger_event(:playlist, @playlist)
|
27
|
+
trigger_event(:player_state, @adapter.playlist) if @adapter
|
28
28
|
|
29
29
|
@playlist
|
30
30
|
end
|
31
31
|
|
32
|
+
def state
|
33
|
+
adapter.playlist
|
34
|
+
end
|
35
|
+
|
32
36
|
=begin
|
33
37
|
Sync checks the current status of the player.
|
34
38
|
Is it paused? Playing? What is it playing?
|
35
39
|
It compares the expected to actual statuses and
|
36
|
-
|
40
|
+
triggers events if there is a difference.
|
37
41
|
=end
|
38
42
|
def sync
|
39
43
|
return false unless adapter?
|
@@ -41,29 +45,52 @@ class Player
|
|
41
45
|
current = adapter.playlist
|
42
46
|
expected = playlist
|
43
47
|
|
44
|
-
|
48
|
+
sync = Radiodan::PlaylistSync.new expected, current
|
49
|
+
synced = sync.sync?
|
45
50
|
|
46
|
-
|
47
|
-
true
|
48
|
-
else
|
51
|
+
unless synced
|
49
52
|
# playback state
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
sync.errors.each do |e|
|
54
|
+
case e
|
55
|
+
when :state
|
56
|
+
logger.debug "Expected State: #{expected.state} Got: #{current.state}"
|
57
|
+
trigger_event :play_state, current.state
|
58
|
+
when :mode
|
59
|
+
logger.debug "Expected Mode: #{expected.mode} Got: #{current.mode}"
|
60
|
+
trigger_event :play_mode, current.mode
|
61
|
+
when :new_tracks
|
62
|
+
logger.debug "Expected: #{expected.current.inspect} Got: #{current.current.inspect}"
|
63
|
+
trigger_event :playlist, expected
|
64
|
+
when :add_tracks
|
65
|
+
logger.debug "Found additional tracks to enqueue"
|
66
|
+
trigger_event :enqueue, expected.tracks[current.tracks.size..-1]
|
67
|
+
trigger_event :play_pending if sync.errors.include?(:state) && current.state == :stop
|
68
|
+
when :volume
|
69
|
+
logger.debug "Expected Volume: #{expected.volume} Got: #{current.volume}"
|
70
|
+
trigger_event :volume, expected.volume
|
71
|
+
end
|
58
72
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
trigger_event :sync, current
|
76
|
+
synced
|
77
|
+
end
|
78
|
+
|
79
|
+
def respond_to?(method)
|
80
|
+
if adapter.respond_to? method
|
81
|
+
true
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def method_missing(method, *args, &block)
|
90
|
+
if adapter.respond_to? method
|
91
|
+
adapter.send method, *args, &block
|
92
|
+
else
|
93
|
+
super
|
67
94
|
end
|
68
95
|
end
|
69
96
|
end
|
data/lib/radiodan/playlist.rb
CHANGED
@@ -29,7 +29,8 @@ class Playlist
|
|
29
29
|
STATES = [:play, :stop, :pause]
|
30
30
|
MODES = [:sequential, :resume, :random]
|
31
31
|
attr_reader :state, :mode, :repeat, :tracks, :position, :seek, :volume
|
32
|
-
|
32
|
+
alias_method :repeat?, :repeat
|
33
|
+
def_delegators :@tracks, :size, :length, :empty?
|
33
34
|
|
34
35
|
def initialize(options={})
|
35
36
|
self.state = options.fetch(:state, STATES.first)
|
@@ -44,7 +45,15 @@ class Playlist
|
|
44
45
|
def current
|
45
46
|
tracks[position]
|
46
47
|
end
|
47
|
-
|
48
|
+
|
49
|
+
def random?
|
50
|
+
self.mode == :random
|
51
|
+
end
|
52
|
+
|
53
|
+
def state
|
54
|
+
empty? ? :stop : @state
|
55
|
+
end
|
56
|
+
|
48
57
|
def state=(new_state)
|
49
58
|
state = new_state.to_sym
|
50
59
|
|
@@ -98,15 +107,27 @@ class Playlist
|
|
98
107
|
end
|
99
108
|
|
100
109
|
def volume=(new_volume)
|
110
|
+
# -1 is allowed when volume cannot be determined
|
101
111
|
begin
|
102
112
|
new_volume = Integer(new_volume)
|
103
113
|
|
104
|
-
raise ArgumentError if new_volume > 100 || new_volume <
|
114
|
+
raise ArgumentError if new_volume > 100 || new_volume < -1
|
105
115
|
rescue ArgumentError
|
106
|
-
raise VolumeError, "#{new_volume} not an integer
|
116
|
+
raise VolumeError, "#{new_volume} not an integer -1-100"
|
107
117
|
end
|
108
118
|
|
109
119
|
@volume = new_volume
|
110
120
|
end
|
121
|
+
|
122
|
+
def attributes
|
123
|
+
{ :state => state,
|
124
|
+
:mode => mode,
|
125
|
+
:repeat => repeat,
|
126
|
+
:tracks => begin tracks.collect(&:attributes) rescue []; end,
|
127
|
+
:position => position,
|
128
|
+
:seek => seek,
|
129
|
+
:volume => volume }
|
130
|
+
end
|
131
|
+
alias_method :as_json, :attributes
|
111
132
|
end
|
112
133
|
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'logging'
|
2
|
+
|
1
3
|
class Radiodan
|
2
4
|
class PlaylistSync
|
5
|
+
include Logging
|
6
|
+
|
3
7
|
class SyncError < Exception; end
|
4
8
|
attr_accessor :expected, :current
|
5
9
|
attr_reader :errors
|
@@ -11,37 +15,57 @@ class PlaylistSync
|
|
11
15
|
end
|
12
16
|
|
13
17
|
def sync?
|
14
|
-
|
15
|
-
|
18
|
+
if ready?
|
19
|
+
compare_playback_state & compare_playback_mode & compare_tracks & compare_volume
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
def ready?
|
24
|
+
if expected.nil? || current.nil?
|
25
|
+
logger.warn 'Require two playlists to compare'
|
26
|
+
false
|
27
|
+
else
|
28
|
+
true
|
29
|
+
end
|
22
30
|
end
|
23
31
|
|
32
|
+
private
|
24
33
|
def compare_playback_state
|
25
34
|
# add rules about when this is ok to be out of sync
|
26
35
|
# e.g. sequential expected runs out of tracks and stops
|
27
|
-
|
36
|
+
report(:state) { @expected.state != @current.state }
|
28
37
|
end
|
29
38
|
|
30
39
|
def compare_playback_mode
|
31
|
-
|
40
|
+
report(:mode) { @expected.mode != @current.mode }
|
32
41
|
end
|
33
42
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
43
|
+
def compare_tracks
|
44
|
+
report(:add_tracks) do
|
45
|
+
# more tracks are added and
|
46
|
+
# original tracks are all in the same position in playlist
|
47
|
+
@expected.size > @current.size && !@current.empty? &&
|
48
|
+
@current.tracks.all? {|x| i=@current.tracks.index(x); @expected.tracks[i] == x }
|
49
|
+
end
|
50
|
+
|
51
|
+
return false if errors.include?(:add_tracks)
|
52
|
+
|
53
|
+
report(:new_tracks) do
|
54
|
+
@expected.size != @current.size ||
|
55
|
+
@expected.tracks != @current.tracks
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def compare_volume
|
60
|
+
report(:volume) do
|
61
|
+
@expected.volume != @current.volume
|
38
62
|
end
|
39
63
|
end
|
40
64
|
|
41
|
-
def
|
65
|
+
def report(type, &blk)
|
42
66
|
result = blk.call
|
43
|
-
errors << type
|
44
|
-
result
|
67
|
+
errors << type if result
|
68
|
+
!result
|
45
69
|
end
|
46
70
|
end
|
47
71
|
end
|
data/lib/radiodan/track.rb
CHANGED
@@ -4,15 +4,18 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
4
4
|
class Radiodan
|
5
5
|
class Track
|
6
6
|
class NoFileError < Exception; end
|
7
|
-
extend
|
7
|
+
extend Forwardable
|
8
|
+
attr_reader :attributes
|
8
9
|
def_delegators :@attributes, :[]
|
9
10
|
|
11
|
+
|
10
12
|
alias_method :eql?, :==
|
11
13
|
|
12
14
|
def initialize(attributes={})
|
13
15
|
@attributes = HashWithIndifferentAccess.new(attributes)
|
14
|
-
|
15
|
-
|
16
|
+
unless @attributes.has_key?(:file)
|
17
|
+
raise NoFileError, 'No file given for track'
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
21
|
def ==(other)
|
data/lib/radiodan/version.rb
CHANGED
data/lib/radiodan.rb
CHANGED
@@ -19,14 +19,13 @@ class Radiodan
|
|
19
19
|
# keep player running on schedule
|
20
20
|
raise "no player set" unless player.adapter?
|
21
21
|
|
22
|
+
stop_player_on_exit
|
23
|
+
|
22
24
|
EM.synchrony do
|
23
25
|
trap_signals!
|
26
|
+
EventMachine::Synchrony.next_tick { @builder.call_middleware! }
|
24
27
|
|
25
|
-
EM::Synchrony.
|
26
|
-
@builder.call_middleware!
|
27
|
-
end
|
28
|
-
|
29
|
-
EM.now_and_every(seconds: 1) do
|
28
|
+
EM::Synchrony.add_periodic_timer(1) do
|
30
29
|
logger.info "SYNC!"
|
31
30
|
player.sync if player
|
32
31
|
end
|
@@ -55,17 +54,18 @@ class Radiodan
|
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
57
|
+
def stop_player_on_exit
|
58
|
+
at_exit do
|
59
|
+
logger.info 'Stopping player'
|
60
|
+
stop
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
58
64
|
def trap_signals!
|
59
65
|
%w{INT TERM SIGHUP SIGINT SIGTERM}.each do |signal|
|
60
66
|
Signal.trap(signal) do
|
61
67
|
logger.info "Trapped #{signal}"
|
62
|
-
EM
|
63
|
-
begin
|
64
|
-
stop
|
65
|
-
ensure
|
66
|
-
EM.stop
|
67
|
-
end
|
68
|
-
end
|
68
|
+
EM.stop
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
data/radiodan.gemspec
CHANGED
@@ -25,7 +25,10 @@ Gem::Specification.new do |gem|
|
|
25
25
|
gem.add_dependency 'eventmachine', EM_VERSION
|
26
26
|
gem.add_dependency 'em-synchrony', EM_VERSION
|
27
27
|
gem.add_dependency 'em-http-request', EM_VERSION
|
28
|
-
gem.add_dependency 'em-simple_telnet',
|
29
|
-
gem.add_dependency 'active_support',
|
30
|
-
gem.add_dependency 'i18n',
|
28
|
+
gem.add_dependency 'em-simple_telnet', '~> 0.0.6'
|
29
|
+
gem.add_dependency 'active_support', '~> 3.0.0'
|
30
|
+
gem.add_dependency 'i18n', '~> 0.6.4'
|
31
|
+
gem.add_dependency 'thin', '~> 1.5.1'
|
32
|
+
gem.add_dependency 'sinatra', '~> 1.4.2'
|
33
|
+
gem.add_dependency 'sinatra-synchrony', '~> 0.4.1'
|
31
34
|
end
|
data/spec/lib/builder_spec.rb
CHANGED
@@ -7,10 +7,10 @@ describe Radiodan::Builder do
|
|
7
7
|
Radiodan::Player.stub(:new).and_return(@player)
|
8
8
|
end
|
9
9
|
|
10
|
-
it 'passes a playlist to the
|
10
|
+
it 'passes a playlist to the correct middleware' do
|
11
11
|
playlist = mock
|
12
12
|
|
13
|
-
|
13
|
+
Radiodan::Builder.any_instance.should_receive(:use).with(:playlist_to_start, playlist)
|
14
14
|
|
15
15
|
builder = Radiodan::Builder.new do |b|
|
16
16
|
b.playlist playlist
|
@@ -30,6 +30,27 @@ describe Radiodan::Builder do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
describe 'logger' do
|
34
|
+
it 'sets a log output and level' do
|
35
|
+
builder = Radiodan::Builder.new do |b|
|
36
|
+
b.log '/dev/null', :fatal
|
37
|
+
end
|
38
|
+
|
39
|
+
Radiodan::Logging.level.should == Logger::FATAL
|
40
|
+
Radiodan::Logging.output.should == '/dev/null'
|
41
|
+
end
|
42
|
+
|
43
|
+
it "has an optional log level" do
|
44
|
+
old_level = Radiodan::Logging.level
|
45
|
+
|
46
|
+
builder = Radiodan::Builder.new do |b|
|
47
|
+
b.log '/dev/null'
|
48
|
+
end
|
49
|
+
|
50
|
+
Radiodan::Logging.level.should == old_level
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
33
54
|
describe 'middleware' do
|
34
55
|
it 'creates an instance of middleware and stores internally' do
|
35
56
|
class Radiodan::MockMiddle; end
|
@@ -40,7 +61,7 @@ describe Radiodan::Builder do
|
|
40
61
|
builder = Radiodan::Builder.new do |b|
|
41
62
|
b.use :mock_middle, options
|
42
63
|
end
|
43
|
-
|
64
|
+
|
44
65
|
builder.middleware.size.should == 1
|
45
66
|
builder.middleware.should include middleware
|
46
67
|
end
|
data/spec/lib/logging_spec.rb
CHANGED
@@ -2,7 +2,12 @@ require 'spec_helper'
|
|
2
2
|
require 'logging'
|
3
3
|
|
4
4
|
describe Radiodan::Logging do
|
5
|
-
it '
|
6
|
-
subject.
|
5
|
+
it 'sets a log level' do
|
6
|
+
subject.level = :warn
|
7
|
+
class Test
|
8
|
+
include Radiodan::Logging
|
9
|
+
end
|
10
|
+
|
11
|
+
Test.new.logger.level.should == Logger::WARN
|
7
12
|
end
|
8
13
|
end
|
data/spec/lib/player_spec.rb
CHANGED
@@ -19,9 +19,12 @@ describe Radiodan::Player do
|
|
19
19
|
|
20
20
|
context 'playlist' do
|
21
21
|
it 'triggers a new playlist event' do
|
22
|
-
playlist =
|
22
|
+
playlist = stub
|
23
|
+
adapter = mock(:player= => nil, :playlist => playlist)
|
23
24
|
|
25
|
+
subject.adapter = adapter
|
24
26
|
subject.should_receive(:trigger_event).with(:playlist, playlist)
|
27
|
+
subject.should_receive(:trigger_event).with(:player_state, playlist)
|
25
28
|
subject.playlist = playlist
|
26
29
|
end
|
27
30
|
end
|
@@ -43,14 +46,15 @@ describe Radiodan::Player do
|
|
43
46
|
subject.sync.should == true
|
44
47
|
end
|
45
48
|
|
46
|
-
context 'sync error triggers events' do
|
49
|
+
context 'sync error triggers events:' do
|
47
50
|
before :each do
|
48
51
|
Radiodan::PlaylistSync.any_instance.stub(:sync?).and_return(false)
|
52
|
+
subject.should_receive(:trigger_event).with(:sync, subject.adapter.playlist)
|
49
53
|
end
|
50
54
|
|
51
55
|
it 'playback state' do
|
52
56
|
Radiodan::PlaylistSync.any_instance.stub(:errors).and_return([:state])
|
53
|
-
subject.playlist.stub(:state => :playing)
|
57
|
+
subject.adapter.playlist.stub(:state => :playing)
|
54
58
|
|
55
59
|
subject.should_receive(:trigger_event).with(:play_state, :playing)
|
56
60
|
subject.sync.should == false
|
@@ -58,21 +62,21 @@ describe Radiodan::Player do
|
|
58
62
|
|
59
63
|
it 'playback mode' do
|
60
64
|
Radiodan::PlaylistSync.any_instance.stub(:errors).and_return([:mode])
|
61
|
-
subject.playlist.stub(:mode => :random)
|
65
|
+
subject.adapter.playlist.stub(:mode => :random)
|
62
66
|
|
63
67
|
subject.should_receive(:trigger_event).with(:play_mode, :random)
|
64
68
|
subject.sync.should == false
|
65
69
|
end
|
66
70
|
|
67
71
|
it 'playlist' do
|
68
|
-
Radiodan::PlaylistSync.any_instance.stub(:errors).and_return([:
|
72
|
+
Radiodan::PlaylistSync.any_instance.stub(:errors).and_return([:new_tracks])
|
69
73
|
|
70
74
|
playlist_content = stub
|
71
75
|
subject.playlist.stub(:content => playlist_content)
|
72
76
|
|
73
77
|
subject.should_receive(:trigger_event).with(:playlist, subject.playlist)
|
74
78
|
subject.sync.should == false
|
75
|
-
end
|
79
|
+
end
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
data/spec/lib/playlist_spec.rb
CHANGED
@@ -3,7 +3,12 @@ require 'playlist'
|
|
3
3
|
|
4
4
|
describe Radiodan::Playlist do
|
5
5
|
describe 'default attributes' do
|
6
|
-
it 'has a state of
|
6
|
+
it 'has a state of stop' do
|
7
|
+
subject.state.should == :stop
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'has a state of play if there are tracks' do
|
11
|
+
subject.tracks << mock
|
7
12
|
subject.state.should == :play
|
8
13
|
end
|
9
14
|
|
@@ -33,9 +38,22 @@ describe Radiodan::Playlist do
|
|
33
38
|
end
|
34
39
|
|
35
40
|
describe 'playback state' do
|
36
|
-
it '
|
37
|
-
subject.
|
38
|
-
|
41
|
+
it 'is always stop if playlist is empty' do
|
42
|
+
subject.empty?.should be_true
|
43
|
+
|
44
|
+
subject.state = :pause
|
45
|
+
subject.state.should == :stop
|
46
|
+
|
47
|
+
subject.tracks << mock
|
48
|
+
|
49
|
+
subject.empty?.should be_false
|
50
|
+
subject.state.should == :pause
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can be set if tracks are present' do
|
54
|
+
subject.tracks << mock
|
55
|
+
subject.state = :pause
|
56
|
+
subject.state.should == :pause
|
39
57
|
end
|
40
58
|
|
41
59
|
it 'cannot be set to an unknown state' do
|
@@ -71,6 +89,17 @@ describe Radiodan::Playlist do
|
|
71
89
|
end
|
72
90
|
end
|
73
91
|
|
92
|
+
describe 'random mode' do
|
93
|
+
it 'is off by default' do
|
94
|
+
subject.random?.should == false
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'can be set' do
|
98
|
+
subject.mode = :random
|
99
|
+
subject.random?.should == true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
74
103
|
describe 'tracks' do
|
75
104
|
it 'creates an array of tracks' do
|
76
105
|
subject.tracks = 'x.mp3'
|
@@ -138,10 +167,23 @@ describe Radiodan::Playlist do
|
|
138
167
|
subject.volume.should == 24
|
139
168
|
end
|
140
169
|
|
141
|
-
it 'has a legal range of
|
170
|
+
it 'has a legal range of -1-100' do
|
142
171
|
expect { subject.volume = '999' }.to raise_error subject.class::VolumeError
|
143
172
|
expect { subject.volume = -29 }.to raise_error subject.class::VolumeError
|
144
173
|
subject.volume.should == 100
|
145
174
|
end
|
146
175
|
end
|
176
|
+
|
177
|
+
describe 'attributes' do
|
178
|
+
it 'should be well formed' do
|
179
|
+
expected = {:state=>:stop, :mode=>:sequential, :repeat=>false, :tracks=>[], :position=>0, :seek=>0.0, :volume=>100}
|
180
|
+
expect subject.attributes.should == expected
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should include track attributes' do
|
184
|
+
subject.tracks << Radiodan::Track.new(:file => 'dan.mp3')
|
185
|
+
expected = {:state=>:play, :mode=>:sequential, :repeat=>false, :tracks=>[{'file' => 'dan.mp3'}], :position=>0, :seek=>0.0, :volume=>100}
|
186
|
+
expect subject.attributes.should == expected
|
187
|
+
end
|
188
|
+
end
|
147
189
|
end
|