about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2015-12-07 03:51:33 +0000
committerEric Wong <e@80x24.org>2015-12-07 05:22:20 +0000
commit3e013d3f24f0f935dce985d4c6bd155889e05dbb (patch)
tree7c9dfae4dc7939265ff4abe2b906ef91a884cb19
parente5668a47f20a6593ab47847083bc704e1abeb1e6 (diff)
downloaddtas-3e013d3f24f0f935dce985d4c6bd155889e05dbb.tar.gz
This is in the MPRIS 2.0 TrackList spec and also in mpd (as "repeat"
mode), so we can probably support it directly in player to ease
implementations of future wrappers.
-rw-r--r--lib/dtas/player/client_handler.rb9
-rw-r--r--lib/dtas/tracklist.rb87
-rw-r--r--test/test_tracklist.rb25
3 files changed, 106 insertions, 15 deletions
diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb
index 0aa264f..500704a 100644
--- a/lib/dtas/player/client_handler.rb
+++ b/lib/dtas/player/client_handler.rb
@@ -584,6 +584,15 @@ module DTAS::Player::ClientHandler # :nodoc:
         return io.emit("repeat #{@tl.repeat.to_s}")
       end
       io.emit("OK")
+    when 'shuffle'
+      v = msg.shift
+      case v
+      when nil then io.emit("shuffle #{(!!@tl.shuffle).to_s}")
+      when 'debug' then io.emit(@tl.shuffle.to_yaml) # TODO: remove
+      else
+        set_bool(io, 'tl shuffle', v) { |b| @tl.shuffle = b }
+        io.emit('OK')
+      end
     when "remove"
       track_id = msg.shift or return io.emit("ERR track_id not specified")
       track_id = track_id.to_i
diff --git a/lib/dtas/tracklist.rb b/lib/dtas/tracklist.rb
index 1ae2719..f3dca31 100644
--- a/lib/dtas/tracklist.rb
+++ b/lib/dtas/tracklist.rb
@@ -9,6 +9,7 @@ require_relative 'track'
 class DTAS::Tracklist # :nodoc:
   include DTAS::Serialize
   attr_accessor :repeat # true, false, 1
+  attr_reader :shuffle  # false or shuffled @list
 
   SIVS = %w(list pos repeat)
   TL_DEFAULTS = {
@@ -20,9 +21,13 @@ class DTAS::Tracklist # :nodoc:
   def self.load(hash)
     obj = new
     obj.instance_eval do
-      list = hash["list"] and @list.replace(list.map! { |s| new_track(s) })
+      list = hash["list"] and @list.replace(list.map { |s| new_track(s) })
       @pos = hash["pos"] || -1
       @repeat = hash["repeat"] || false
+      if hash['shuffle']
+        @shuffle = @list.shuffle
+        @pos = _idx_of(@shuffle, @list[@pos].track_id) if @pos >= 0
+      end
     end
     obj
   end
@@ -31,6 +36,10 @@ class DTAS::Tracklist # :nodoc:
     h = ivars_to_hash(SIVS)
     h.delete_if { |k,v| TL_DEFAULTS[k] == v }
     list = h['list'] and h['list'] = list.map(&:to_path)
+    if @shuffle
+      h['shuffle'] = true
+      h['pos'] = _idx_of(@list, @shuffle[@pos].track_id) if @pos >= 0
+    end
     h
   end
 
@@ -39,6 +48,7 @@ class DTAS::Tracklist # :nodoc:
     @list = []
     @goto_off = @goto_pos = nil
     @track_nr = 0
+    @shuffle = false
   end
 
   def new_track(path)
@@ -54,6 +64,7 @@ class DTAS::Tracklist # :nodoc:
   def reset
     @goto_off = @goto_pos = nil
     @pos = TL_DEFAULTS["pos"]
+    @shuffle.shuffle! if @shuffle
   end
 
   def get_tracks(track_ids)
@@ -66,37 +77,66 @@ class DTAS::Tracklist # :nodoc:
     rv
   end
 
+  def _update_pos(pos, prev, list)
+    old = prev[pos]
+    _idx_of(list, old.track_id)
+  end
+
+  def shuffle=(bool)
+    prev = @shuffle
+    if bool
+      list = @shuffle = (prev ||= @list).shuffle
+    else
+      list = @list
+      @shuffle = false
+    end
+    @pos = _update_pos(@pos, prev, list) if @pos >= 0
+    @goto_pos = _update_pos(@goto_pos, prev, list) if @goto_pos
+  end
+
   def tracks
     @list.map(&:track_id)
   end
 
   def advance_track(repeat_ok = true)
-    return if @list.empty?
+    cur = @shuffle || @list
+    return if cur.empty?
     # @repeat == 1 for single track repeat
     repeat = repeat_ok ? @repeat : false
     next_pos = @goto_pos || @pos + (repeat == 1 ? 0 : 1)
     next_off = @goto_off # nil by default
     @goto_pos = @goto_off = nil
-    if @list[next_pos]
+    if cur[next_pos]
       @pos = next_pos
     elsif repeat
       next_pos = @pos = 0
     else
       return
     end
-    [ @list[next_pos].to_path, next_off ]
+    [ cur[next_pos].to_path, next_off ]
   end
 
   def cur_track
-    @pos >= 0 ? @list[@pos] : nil
+    @pos >= 0 ? (@shuffle || @list)[@pos] : nil
   end
 
   def add_track(track, after_track_id = nil, set_as_current = false)
     track = new_track(track)
     if after_track_id
-      idx = _idx_of(after_track_id) or
-        raise ArgumentError, "after_track_id invalid"
+      idx = _idx_of(@list, after_track_id) or
+                                  raise ArgumentError, 'after_track_id invalid'
+      if @shuffle
+        _idx_of(@shuffle, after_track_id) or
+                                  raise ArgumentError, 'after_track_id invalid'
+      end
       @list[idx, 1] = [ @list[idx], track ]
+
+      # add into random position if shuffling
+      if @shuffle
+        idx = rand(@shuffle.size)
+        @shuffle[idx, 1] = [ @shuffle[idx], track ]
+      end
+
       if set_as_current
         @pos = idx + 1
       else
@@ -104,21 +144,37 @@ class DTAS::Tracklist # :nodoc:
       end
     else # nil = first_track
       @list.unshift(track)
-      if set_as_current
-        @pos = 0
+
+      if @shuffle
+        if @shuffle.empty?
+          @shuffle << track
+          @pos = 0 if set_as_current
+        else
+          idx = rand(@shuffle.size)
+          @shuffle[idx, 1] = [ @shuffle[idx], track ]
+          @pos = idx + 1 if set_as_current
+        end
       else
-        @pos += 1 if @pos >= 0
+        if set_as_current
+          @pos = 0
+        else
+          @pos += 1 if @pos >= 0
+        end
       end
     end
     track.track_id
   end
 
-  def _idx_of(track_id)
-    @list.index { |t| t.track_id == track_id }
+  def _idx_of(list, track_id)
+    list.index { |t| t.track_id == track_id }
   end
 
   def remove_track(track_id)
-    idx = _idx_of(track_id) or return false
+    idx = _idx_of(@list, track_id) or return false
+    if @shuffle
+      si = _idx_of(@shuffle, track_id) or return false
+      @shuffle.delete_at(si)
+    end
     track = @list.delete_at(idx)
     len = @list.size
     if @pos >= len
@@ -129,9 +185,10 @@ class DTAS::Tracklist # :nodoc:
   end
 
   def go_to(track_id, offset_hhmmss = nil)
-    if idx = _idx_of(track_id)
+    list = @shuffle || @list
+    if idx = _idx_of(list, track_id)
       @goto_off = offset_hhmmss
-      return @list[@goto_pos = idx].to_path
+      return list[@goto_pos = idx].to_path
     end
     @goto_pos = nil
     # noop if track_id is invalid
diff --git a/test/test_tracklist.rb b/test/test_tracklist.rb
index 33184c6..a3c89f6 100644
--- a/test/test_tracklist.rb
+++ b/test/test_tracklist.rb
@@ -61,6 +61,31 @@ class TestTracklist < Testcase
     assert_equal 'g', tl.advance_track[0]
   end
 
+  def test_shuffle
+    tl = DTAS::Tracklist.new
+    exp = %w(a b c d e f g)
+    list_add(tl, exp)
+    tl.shuffle = true
+    assert_equal(exp, list_to_path(tl))
+    assert_equal exp.size, tl.shuffle.size
+    assert_equal exp, tl.shuffle.map(&:to_path).sort
+    tl.shuffle = false
+    assert_equal false, tl.shuffle
+
+    tl.instance_variable_set :@pos, 3
+    before = tl.cur_track
+    3.times do
+      tl.shuffle = true
+      assert_equal before, tl.cur_track
+    end
+    x = tl.to_hsh
+    assert_equal true, x['shuffle']
+    3.times do
+      loaded = DTAS::Tracklist.load(x.dup)
+      assert_equal before.to_path, loaded.cur_track.to_path
+    end
+  end
+
   def test_remove_track
     tl = DTAS::Tracklist.new
     ary = %w(a b c d e f g)