From 571d07e99bc0051ad51e1f5947a0ad28eefb557f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 30 Sep 2013 03:26:00 +0000 Subject: player: support seeking based on embedded cuesheet (FLAC) This adds the ability to seek internally within FLAC file based on the internal CUE sheet. Other formats may be supported in the future, but FLAC is the only one I know of which supports embedded cue sheets. Note: flac 1.3.0 is recommended for users of non-CDDA-compatible formats. See updates to dtas-player_protocol(7) for details. --- lib/dtas/player/client_handler.rb | 79 +++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) (limited to 'lib/dtas/player/client_handler.rb') diff --git a/lib/dtas/player/client_handler.rb b/lib/dtas/player/client_handler.rb index 91dfada..2f356ab 100644 --- a/lib/dtas/player/client_handler.rb +++ b/lib/dtas/player/client_handler.rb @@ -351,6 +351,16 @@ module DTAS::Player::ClientHandler # :nodoc: @paused ? do_play : do_pause end + def seek_internal(cur, offset) + if cur.requeued + @queue[0][1] = offset + else + @queue.unshift([ cur.infile, offset ]) + cur.requeued = true + __buf_reset(cur.dst) # trigger EPIPE + end + end + def do_seek(io, offset) if @current if @current.respond_to?(:infile) @@ -364,13 +374,7 @@ module DTAS::Player::ClientHandler # :nodoc: rescue ArgumentError return io.emit("ERR bad time format") end - if @current.requeued - @queue[0][1] = offset - else - @queue.unshift([ @current.infile, offset ]) - @current.requeued = true - __buf_reset(@current.dst) # trigger EPIPE - end + seek_internal(@current, offset) else return io.emit("ERR unseekable") end @@ -607,5 +611,66 @@ module DTAS::Player::ClientHandler # :nodoc: io.emit("OK") end end + + def __bp_prev_next(io, msg, cur, bp) + case type = msg[1] + when nil, "track" + bp.keep_if { |ci| ci.track? } + when "pregap" + bp.keep_if { |ci| ci.pregap? } + when "subindex" # any subindex + bp.keep_if { |ci| ci.subindex? } + when /\A\d+\z/ # exact subindex match + si = type.to_i + bp.keep_if { |ci| ci.index == si } + when "any" # anything goes + else + return io.emit("INVALID TYPE") + end + fmt = cur.format + case msg[0] + when "next" + ds = __current_decoded_samples + bp.each do |ci| + next if ci.offset_samples(fmt) < ds + seek_internal(cur, ci.offset) + return io.emit("OK") + end + # go to the next (real) track if not found + __current_drop + when "prev" + os = cur.offset_samples # where we currently started + bp.reverse_each do |ci| + next if ci.offset_samples(fmt) >= os + seek_internal(cur, ci.offset) + return io.emit("OK") + end + # offset may be nil/zero if we couldn't find a previous breakpoint + seek_internal(cur, '0') + end + io.emit("OK") + end + + def cue_handler(io, msg) + cur = @current + if cur.respond_to?(:cuebreakpoints) + offset = nil + bp = cur.cuebreakpoints + case cmd = msg[0] + when nil + tmp = { "infile" => cur.infile, "cue" => bp.map { |ci| ci.to_hash } } + io.emit(tmp.to_yaml) + when "next", "prev" + return __bp_prev_next(io, msg, cur, bp) + when "goto" + index = msg[1] or return io.emit("NOINDEX") + ci = bp[index.to_i] or return io.emit("BADINDEX") + seek_internal(cur, ci.offset) + return io.emit("OK") + end + else + io.emit("NOCUE") + end + end end # :startdoc: -- cgit v1.2.3-24-ge0c7