The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# $Id: MPlayerEmbed.pm,v 1.7 2006/01/02 19:44:41 jodrell Exp $
# Copyright (c) 2005 Gavin Brown. All rights reserved. This program is free
# software; you can redistribute it and/or modify it under the same terms as
# Perl itself.
package Gtk2::Ex::MPlayerEmbed;
use constant {
	PAUSE		=> 'pause',
	RESUME		=> 'pause', # 'pause' is really a toggle
	CLOSE		=> 'quit',
};
use Carp;
use FileHandle;
use Gtk2;
use vars qw($VERSION $STATE_ENUM_PKG);
use strict;

our $VERSION = '0.02';


BEGIN {
	our $STATE_ENUM_PKG = sprintf('%s::PlayingState', __PACKAGE__);
	Glib::Type->register_enum(
		$STATE_ENUM_PKG,
		'stopped', 'playing', 'paused'
	);
}

*new = \&Glib::Object::new;

=pod

=head1 NAME

Gtk2::Ex::MPlayerEmbed - a widget to embed the MPlayer media player into GTK+ applications

=head1 SYNOPSIS

	use Gtk2::Ex::MPlayerEmbed;

	my $window = Gtk2::Window->new;

	my $embed = Gtk2::MPlayerEmbed->new;

	$window->add($embed);

	$window->show_all;

	$embed->play("movie.mpg");

	Gtk2->main;

=head1 DESCRIPTION

Gtk2::Ex::MPlayerEmbed allows you to embed a video player into your
applications. It uses the XEMBED system to allow the I<mplayer> program
to insert its window into your application.

=head1 OBJECT HIERARCHY

  Glib::Object
  +----Gtk2::Object
       +----Gtk2::Widget
            +----Gtk2::Container
                 +----Gtk2::Socket
                      +----Gtk2::Ex::MPlayerEmbed

=head1 PROPERTIES

The following properties are accessible through the standard Glib C<get()> and C<set()> methods:

=over


=item C<mplayer_path>

This is the path to the I<mplayer> program. This is C</usr/bin/mplayer> by default.

=item C<args>

This is a string containing the command line arguments passed to I<mplayer> (no
default).

=item C<loaded>

This is a B<boolean> value that indicates whether the I<mplayer> program is
currently running.

=item C<state>

This is an B<enumeration> (described by C<Gtk2::Ex::MPlayerEmbed::PlayingState>)
that indicates the state of the player. C<state> may be one of: C<stopped>,
C<playing>, C<paused>. It is C<stopped> at startup.

=back

=cut

Glib::Type->register(
	Gtk2::Socket::,
	__PACKAGE__,
	properties => [
		Glib::ParamSpec->string(
			'mplayer_path',
			'MPlayer Path',
			'Path to the MPlayer program',
			'/usr/bin/mplayer',
			[qw/readable writable/],
		),

		Glib::ParamSpec->string(
			'args',
			'MPlayer Arguments',
			'The arguments supplied to the mplayer command',
			'',
			[qw/readable writable/],
		),

		Glib::ParamSpec->boolean(
			'loaded',
			'Loaded Flag',
			'Do we have an input loaded?',
			0,
			[qw/readable writable/],
		),

		Glib::ParamSpec->enum(
			'state',
			'Playing state',
			'The current state of the player',
			$STATE_ENUM_PKG,
			'stopped',
			[qw/readable writable/],
		),
	],
);

sub INIT_INSTANCE {
	my $self = shift;
	$self->modify_bg('normal', Gtk2::Gdk::Color->new(0, 0, 0));
	$self->{slave} = FileHandle->new;
	$self->slave->autoflush(1);
	return 1;
}

=pod

=head1 METHODS

	$embed->play([$content]);

This method has two behaviours: if the the C<loaded> property is true (a video
stream has been loaded), and the C<state> property is C<paused>, then it will
resume playing the stream. If C<loaded> is true but the stream is B<not>
paused, then the method will carp() and return undef.

If C<load> is false, and the C<$content> argument is defined, then the player
will attempt to load and play the stream identified by C<$content>, which may
be a path to a file, the URL of a network resource, or a "meta-URI" such as
C<dvd://> or C<dvb://>.

=cut

sub play {
	my ($self, $file) = @_;
	if ($self->get('loaded') && $self->get('state') ne 'stopped') {
		if ($self->get('state') eq 'playing') {
			carp("Can't play while still playing, use stop() first.");
			return undef;

		} else {
			return $self->resume;

		}

	} else {
		my $cmd = sprintf(
			'|%s -slave -wid %d -geometry %dx%d %s "%s" 1>/dev/null 2>/dev/null',
			$self->get('mplayer_path'),
			$self->get_id,
			$self->allocation->width,
			$self->allocation->height,
			$self->get('args'),
			$file
		);
		if (!$self->slave->open($cmd)) {
			croak("Cannot open '$cmd': $!");
			return undef;

		} else {
			$self->set('loaded',	1);
			$self->set('state',	'playing');
			return 1;

		}
	}
}

=pod

	$embed->pause;

This method will pause the current video stream. If the stream is not playing,
this method will carp() and return undef.

=cut

sub pause {
	my $self = shift;
	if (!$self->get('loaded') || $self->get('state') eq 'stopped') {
		carp("Player must be loaded and playing before it can pause/resume.");
		return undef;

	} else {
		$self->set('state', 'paused');
		return $self->tell_slave(PAUSE);

	}
}

=pod

	$embed->resume;

This is just a convenience wrapper around C<pause()>. The C<pause()> method is
really a toggle, and two subsequent calls to C<pause()> will pause and then
resume the stream, so this method exists to disambiguate.

=cut

sub resume { $_[0]->pause }

=pod

	$embed->stop;

This method tells I<mplayer> to quit, and resets the widget's internal state.
Before loading another video stream with C<play()>, use this method first.

=cut

sub stop {
	my $self = shift;
	if (!$self->get('loaded') || $self->get('state') eq 'stopped') {
		carp("Can't stop an unloaded or stopped stream.");

	} else {
		$self->tell_slave(CLOSE);
		$self->slave->close;
		$self->{slave} = FileHandle->new;
		$self->set('loaded', undef);
		$self->set('state', 'stopped');

	}
}

=pod

	$embed->tell_slave($something);

This method sends a command to the I<mplayer> slave process. The available
commands are documented in the mplayer-slave-spec.txt file in the source
distribution.

=cut

sub tell_slave {
	my ($self, $msg) = @_;
	return $self->slave->print("$msg\n");
}

sub slave {
	return $_[0]->{slave};
}

=pod

=head1 PREREQUISITES

=over

=item L<Gtk2>

=item The I<mplayer> program, available from L<http://www.mplayerhq.hu/>.

=back

=head1 TODO

1. Do something about controlling aspect ration. We need a way to get the
aspect ratio of the video stream, and use a L<Gtk2::AspectFrame> to constrain
the shape of the widget.

2. Implement more convenience wrappers around the mplayer command set.

2. Implement a C<stream_ended> signal that watches the I<mplayer> process and
emits when it quits.

=head1 SEE ALSO

L<GStreamer>, for a much more powerful, general purpose multimedia system
that's compatible with GTK+.

=head1 AUTHOR

Gavin Brown (gavin dot brown at uk dot com)  

=head1 COPYRIGHT

(c) 2005 Gavin Brown. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.     

=cut

1;