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
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 |bus, message|
handle_bus_message( message )
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.merge( pd )
end
#set or get the volume
def volume(val)
if !val.nil? and val>=0 and val<=1
@playbin.set_property("volume", val)
end
return @playbin.get_property("volume")
end
def seek_percent(val)
if !val.nil? and val>=0 and val<=1
pd = position_duration()
duration = pd['duration']
if duration > 0
seek_loc = val*duration * 1000000
seek = Gst::EventSeek.new(1.0, Gst::Format::Type::TIME, Gst::Seek::FLAG_FLUSH.to_i | Gst::Seek::FLAG_KEY_UNIT.to_i, Gst::Seek::TYPE_SET, seek_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_message( message )
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"
s = $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.
You may want to use "playbin2" instead of "playbin".