kaspernj/pulseaudio

View on GitHub
include/pulseaudio_sink_input.rb

Summary

Maintainability
A
3 hrs
Test Coverage
#Class for controlling inputs.
class PulseAudio::Sink::Input
  #The arguments-hash. Contains various data for the input.
  attr_reader :args
  
  @@inputs = Wref::Map.new
  
  #Starts automatically redirect new opened inputs to the default sink.
  #===Examples
  # PulseAudio::Sink::Input.auto_redirect_new_inputs_to_default_sink
  def self.auto_redirect_new_inputs_to_default_sink
    raise "Already redirecting!" if @auto_redirect_connect_id
    
    @auto_redirect_connect_id = PulseAudio::Events.instance.connect(:event => :new, :element => "sink-input") do |data|
      begin
        sink_input = PulseAudio::Sink::Input.by_id(data[:args][:element_id])
        sink_input.sink = PulseAudio::Sink.by_default
      rescue NameError
        #sometimes sinks are killed instantly and we cant find them before that happens.
      end
    end
  end
  
  #Stops automatically redirecting new opened inputs to the default sink.
  #===Examples
  # PulseAudio::Sink::Input.stop_auto_redirect_new_inputs_to_default_sink
  def self.stop_auto_redirect_new_inputs_to_default_sink
    raise "Not redirecting at the moment." if !@auto_redirect_connect_id
    PulseAudio::Events.instance.unconnect(@auto_redirect_connect_id)
    @auto_redirect_connect_id = nil
  end
  
  #Returns a list of sink-inputs.
  def self.list
    list = %x[pactl list sink-inputs]
    inputs = [] unless block_given?
    
    list.scan(/(\n|^)Sink Input #(\d+)\s+([\s\S]+?)(\n\n|\Z)/) do |match|
      props = {}
      match[2].scan(/(\s+|^)([A-z]+?): (.+?)\n/) do |match_prop|
        props[match_prop[1].downcase] = match_prop[2]
      end

      match[2].scan(/(\t\t|^)([A-z\.]+?) = (.+?)\n/) do |match_prop|
        props[match_prop[1].downcase.gsub(/\./, "_")] = match_prop[2].gsub(/\"/, "")
      end

      input_id = match[1].to_i
      args = {:input_id => input_id, :props => props}

      input = @@inputs[input_id]
      if !input
        input = PulseAudio::Sink::Input.new
        @@inputs[input_id] = input
      end

      input.update(args)

      if block_given?
        yield(input)
      else
        inputs << input
      end
    end
    
    if block_given?
      return nil
    else
      return inputs
    end
  end
  
  #Returns a sink-input by its input-ID.
  #===Examples
  # sink_input = PulseAudio::Sink::Input.by_id(53)
  def self.by_id(id)
    #Return it from the weak-reference-map, if it already exists there.
    if input = @@inputs[id]
      return input
    end
    
    #Read the inputs one-by-one and return it when found.
    PulseAudio::Sink::Input.list do |input|
      return input if input.input_id == id
    end
    
    #Input could not be found by the given ID - raise error.
    raise NameError, "No sink-input by that ID: '#{id}' (#{id.class.name})."
  end
  
  #Should not be called manually but through 'list'.
  def update(args)
    @args = args
  end
  
  #Returns the name of the input as a string.
  def name
    if app_name = @args[:props]["application_name"].to_s.strip and !app_name.empty?
      return app_name
    else
      raise "Could not figure out the input-name."
    end
  end
  
  #Returns the input-ID.
  def input_id
    return @args[:input_id]
  end
  
  #Moves the output to a new sink.
  def sink=(newsink)
    %x[pacmd move-sink-input #{self.input_id} #{newsink.sink_id}]
    return nil
  end
  
  #Returns true if the sink is muted. Otherwise false.
  #===Examples
  # sink.muted? #=> false
  def muted?
    @args[:props]["mute"] == "yes"
  end
  
  #Toggles the mute-functionality of the sink. If it is muted: unmutes. If it isnt muted: mutes.
  #===Examples
  # sink.mute_toggle #=> nil
  def mute_toggle
    self.mute = !self.muted?
    return nil
  end
  
  #Sets the mute to something specific.
  #===Examples
  # sink.mute = true #=> nil
  def mute=(val)
    if val
      %x[pactl set-sink-input-mute #{self.input_id} 1]
      @args[:props]["mute"] = "yes"
    else
      %x[pactl set-sink-input-mute #{self.input_id} 0]
      @args[:props]["mute"] = "no"
    end
    
    return nil
  end
  
  #Increases the volume of the sink by 5%.
  #===Examples
  # sink.vol_incr if sink.active? #=> nil
  def vol_incr
    %x[pactl set-sink-input-volume #{self.input_id} -- +5%]
    new_vol = vol_perc + 5
    new_vol = 100 if new_vol > 100
    @args[:props]["volume"] = "0:  #{new_vol}% 1:  #{new_vol}%"
    return nil
  end
  
  #Decreases the volume of the sink by 5%.
  #===Examples
  # sink.vol_decr if sink.active? #=> nil
  def vol_decr
    %x[pactl set-sink-input-volume #{self.input_id} -- -5%]
    new_vol = vol_perc - 5
    new_vol = 0 if new_vol < 0
    @args[:props]["volume"] = "0:  #{new_vol}% 1:  #{new_vol}%"
    return nil
  end
  
  def vol_perc=(newval)
    newval = newval.to_i
    %x[pactl set-sink-input-volume #{self.input_id} #{newval}%]
    @args[:props]["volume"] = "0:  #{newval}% 1:  #{newval}%"
    return nil
  end
  
  #Returns the current percent of the volume.
  def vol_perc
    if match = @args[:props]["volume"].to_s.match(/(\d+):\s*([\d\.]+)%/)
      return match[2].to_i
    end
    
    raise "Could not figure out the volume from properties: '#{@args[:props]}'."
  end

  def method_missing(meth, *args, &block)
    @args[:props][meth.to_s]
  end
end