subscribe
Tags:
 
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008
2011-03-07

The Need

For part of a Ruby project I've been working on, I needed a way to play audio files, and since I am using the project to teach myself the basics of Ruby programming, I decided to write my own audio player class utilizing the Gstreamer Multimedia framework.

Having written what is essentially the same code in both Vala and Python programming languages, I thought this would be fairly simple with the aid of the documentation.

RTFM (a rant)

The documentation for using Gstreamer with Ruby is part of the Ruby Gnome2 documentation and I found it to be dreadful to use. Since it is difficult to actually find a link to the Gstreamer related documents, I'll include a link http://ruby-gnome2.sourceforge.jp/hiki.cgi?Ruby/GStreamer

Honestly, I tried to read the documentation and it was so frustrating that I started to hate Ruby. What bothered me the most about the documentation wasn't the abundant amount of missing information, it was the 500 Server Error that I would see every 4 out of 5 clicks. Why someone thought it would be a good idea to server the files as a CGI wiki and not as good old static files is beyond me. Aaahhhhhhh! I hate that crap!

OK, time to relax and just look at some code.

Enter The Ruby

#!/usr/bin/env ruby
require 'thread'
require 'gst' #gem install gstreamer
#the gst namespace is Gst
#initialize gst
Gst.init
class Player
  def initialize()
    #create a thread for a glib main loop
    thread Thread.new() do
      @mainloop GLib::MainLoop.new
      @mainloop.run
    end
    #make a few queries
    @query_position Gst::QueryPosition.new(Gst::Format::TIME)
    @query_duration Gst::QueryDuration.new(Gst::Format::TIME)
    #make the playbin
    @playbin Gst::ElementFactory.make("playbin")
    #get the playbins bus
    bus @playbin.bus
    #watch the bus for messages
    bus.add_watch do |busmessage|
      handle_bus_messagemessage )
    end
  end

  #we will need to get the current position and duration of the playbin 
  def position_duration()
    begin
      #run the query
      @playbin.query@query_position )
      #what is that, picoseconds? I'll take milliseconds thank you.
      position position @query_position.parse[1] / 1000000
      @playbin.query@query_duration )
      duration @query_duration.parse[1] / 1000000
    rescue
      position 0
      duration 0
    end
    return {'position'=>position,'duration'=>duration}
  end

  def status()
    #get the state
    bin_state @playbin.get_state
    #isn't there a better way to convert the state to a string?
    case bin_state[1]
      when Gst::State::NULL
        state 'NULL'
      when Gst::State::PAUSED
        state 'PAUSED'
      when Gst::State::PLAYING
        state 'PLAYING'
      when Gst::State::READY
        state 'READY'
    end
    volume @playbin.get_property("volume")
    uri @playbin.get_property("uri")
    pd position_duration()
    #return state, volume
    status_hash = {'state'=>state'volume'=>volume'uri'=>uri}
    #add the position and duration to the hash, and return
    return status_hash.mergepd )
  end

  #set or get the volume
  def volume(val)
    if !val.nil? and val>=and val<=1
      @playbin.set_property("volume"val
    end
    return @playbin.get_property("volume")
  end
  
  def seek_percent(val)
    if !val.nil? and val>=and val<=1
      pd position_duration()
      duration pd['duration']
      if duration 0
        seek_loc val*duration 1000000
        seek Gst::EventSeek.new(1.0Gst::Format::Type::TIMEGst::Seek::FLAG_FLUSH.to_i Gst::Seek::FLAG_KEY_UNIT.to_iGst::Seek::TYPE_SETseek_loc Gst::Seek::TYPE_NONE, -1)
        @playbin.send_event(seek)
      end
    end
    return position_duration()
  end
  
  def quit()
    @playbin.stop
    @mainloop.quit
    #I thought no one liked a quitter?
  end
  
  def set_uri(uri)
    #null the playbin state
    @playbin.set_state(Gst::State::NULL)
    #set the uri
    @playbin.set_property("uri",uri)
  end
  
  def play()
    #really? just play
    @playbin.play
  end

  def pause()
    #really? just play
    @playbin.pause
  end

  def handle_bus_messagemessage )
    case message.type
      when Gst::Message::Type::ERROR
        #null the pipeline
        @playbin.set_state(Gst::State::NULL)
        #TODO: send a signal that playing is finished

      when Gst::Message::Type::EOS
        #null the pipeline
        @playbin.set_state(Gst::State::NULL);
        #TODO: send a signal that playing is finished

      when  Gst::Message::Type::TAG
        tag_list message.parse()
        #we need to get the key and value from the tag
        tag_list.each do |key,val|
          #TODO: store some of this data
        end

      when Gst::Message::Type::STATE_CHANGED
        state @playbin.get_state
      else
        #what should we do?
    end
    #return true or shit breaks: why is this? 
    true
  end
end

if \_\_FILE__ == $0
  input ARGV[0]
  if input.match(/^http:\/\//
    #why the hell doesn't this work?
    uri input
  else
    uri "file://"+File.absolute_path(ARGV[0])
  end
  player Player.new 
  player.set_uri(uri)
  player.play()
  loop true
  sleep 1
  while loop  
    puts "type 'quit' to quit"
    $stdin.gets.chomp
    if s.eql? "quit"
      loop false
    end
  end
  player.quit()
end

For some reason the code will not play an audio file over HTTP and this bothered me for a bit, then I decided that I just don't care. One thing you may notice is that this class will create a new thread for running a GLib mainloop. Had this class been part of a larger project that uses a GLib mainloop, the new thread probably wouldn't be necessary, but hey, I'm not writing a GLib based project.

Comments
2011-05-29 rekado:
re: HTTP streaming:
You may want to use "playbin2" instead of "playbin".
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 1
Answer:
required