package Video::PlaybackMachine::Player; #### #### Video::PlaybackMachine::Player #### #### A POE::Session which displays movies and still frames onscreen #### based on events. #### use strict; use base 'Exporter'; our @EXPORT_OK = qw(PLAYER_STATUS_STOP PLAYER_STATUS_PLAY PLAYER_STATUS_STILL PLAYBACK_OK PLAYBACK_ERROR PLAYBACK_STOPPED); use POE; use X11::FullScreen; use Video::Xine; use Video::PlaybackMachine::EventWheel::FullScreen; use Video::PlaybackMachine::Config; use Log::Log4perl; use Carp; ############################# Class Constants ################################ ## Status codes Xine will report use constant PLAYER_STATUS_STOP => 0; use constant PLAYER_STATUS_PLAY => 1; ## How-the-movie-played status codes # OK == played through and stopped at the end use constant PLAYBACK_OK => 1; # ERROR == problem in trying to play use constant PLAYBACK_ERROR => 2; use constant X_DISPLAY => Video::PlaybackMachine::Config->config()->x_display(); ## Types of playback use constant PLAYBACK_TYPE_MUSIC => 0; use constant PLAYBACK_TYPE_MOVIE => 1; ############################## Class Methods ################################# ## ## new() ## ## Returns a new instance of Player. Note that the session is not created ## until you call spawn(). ## sub new { my $type = shift; my $self = { logger => Log::Log4perl->get_logger('Video.PlaybackMachine.Player'), }; bless $self, $type; } ############################## Session Methods ############################### ## ## On session start, initializes Xine and prepares it to start playing. ## sub _start { my $kernel = $_[KERNEL]; $kernel->alias_set('Player'); my $display = X11::FullScreen::Display->new(X_DISPLAY); $_[HEAP]->{'display'} = $display; $_[HEAP]->{'window'} = $display->createWindow(); $display->sync(); my $xine = Video::Xine->new(); my $config = Video::PlaybackMachine::Config->config(); $xine->set_param(XINE_ENGINE_PARAM_VERBOSITY, $config->player_verbose()); $_[HEAP]->{'xine'} = $xine; my $x11_visual = Video::Xine::Util::make_x11_visual($display, $display->getDefaultScreen(), $_[HEAP]->{'window'}, $display->getWidth(), $display->getHeight(), $display->getPixelAspect() ); # TODO Move "auto" to config file my $driver = Video::Xine::Driver::Video->new($xine,"auto",1,$x11_visual); my $s = $xine->stream_new(undef, $driver) or croak "Unable to open video stream"; $_[OBJECT]->{'stream'} = $s; $_[HEAP]->{'stream_queue'} = Video::PlaybackMachine::Player::EventWheel->new($s); my $fq = Video::PlaybackMachine::EventWheel::FullScreen->new($display, $_[HEAP]->{'window'}); $fq->set_expose_handler( sub { $s->get_video_port()->send_gui_data(XINE_GUI_SEND_EXPOSE_EVENT, $_[1]); } ); $fq->spawn(); $_[HEAP]->{'fullscreen_queue'} = $fq } ## ## Responds to a 'play' request by playing a movie on Xine. ## Arguments: ## ARG0: $postback -- what to call after the play is completed ## ARG1: $offset -- number of seconds after the movie's start to begin ## ARG2: @filenames -- ARG1 onward contains the files to play, in order. ## ## After Xine is started, we'll check on it every $XINE_CHECK_INTERVAL ## seconds to see if it has stopped. ## sub play { my ($kernel, $self, $heap, $postback, $offset, @files) = @_[KERNEL, OBJECT, HEAP, ARG0, ARG1, ARG2 .. $#_ ]; defined $offset or $offset = 0; my $log = $_[OBJECT]{'logger'}; # TODO Remove fatal @files or die "No files specified! stopped"; # Stop if we're playing if ( $self->{'stream'}->get_status() == XINE_STATUS_PLAY ) { $self->{'stream'}->stop(); $self->{'stream'}->close(); } # Clear out any previous events $heap->{'stream_queue'}->clear_events(); $log->info("Playing $files[0]"); my $s = $_[OBJECT]->{'stream'}; $s->open($files[0]) or do { $log->error("Unable to open '$files[0]': Error " . $s->get_error()); $postback->(PLAYBACK_ERROR); return; }; $s->play(0,$offset * 1000) or do { $log->error("Unable to play '$files[0]': Error " . $s->get_error()); $postback->(PLAYBACK_ERROR); return; }; # Tell the system to refresh the window # Drawable changed $s->get_video_port()->send_gui_data(XINE_GUI_SEND_DRAWABLE_CHANGED, $heap->{'window'}); $s->get_video_port()->send_gui_data(XINE_GUI_SEND_VIDEOWIN_VISIBLE, 1); # Spawn a watcher to call the postback after the fact $heap->{'stream_queue'}->set_stop_handler($postback); $heap->{'stream_queue'}->spawn(); $heap->{'playback_type'} = PLAYBACK_TYPE_MOVIE; } ## ## stop() ## ## Stops the currently-playing movie. ## sub stop { # Stop if we're playing if ( $_[OBJECT]->{'stream'}->get_status() == XINE_STATUS_PLAY ) { $_[OBJECT]->{'stream'}->stop(); } } ## ## play_still() ## ## Arguments: ## STILL_FILE: Filename of our stillstore. ## ## Responds to a 'play_still' request by playing a still frame ## on Xine. The stillframe will remain there until something ## replaces it. ## sub play_still { my ($self, $kernel, $heap, $still, $callback, $time) = @_[OBJECT, KERNEL, HEAP, ARG0, ARG1]; my $log = $self->{'logger'}; if ($self->{'stream'}->get_status() == XINE_STATUS_PLAY && $heap->{'playback_type'} == PLAYBACK_TYPE_MOVIE ) { $log->error("Attempted to show still '$still' while playing a movie"); return; } $log->debug("Showing still '$_[ARG0]'"); eval { $heap->{'display'}->displayStill($heap->{'window'}, $still); }; if ($@) { $log->error("Error displaying still '$still': $@"); $callback->(PLAYBACK_ERROR); return; } if (defined $time) { POE::Session->create( inline_states => { _start => sub { $_[KERNEL]->delay('end_delay', $time); }, end_delay => sub { $log->debug("Still playback finished for '$still'"); $callback->($still, PLAYBACK_OK); } } ); } } ## ## play_music() ## ## Arguments: ## ARG0 -- callback. What to call when the music's over. ## ARG1 -- song file. Filename of the song to play. ## ## Responds to a 'play_music' request by playing a particular song. ## Logs a warning and does nothing if we tried to play music during a ## movie. If a song was already playing, lets it play, but substitutes ## the current callback. ## sub play_music { my ($self, $heap, $kernel, $callback, $song_file) = @_[OBJECT,HEAP,KERNEL,ARG0,ARG1]; defined $callback or die "Must define callback!\n"; defined $song_file or die "Must define song file!\n"; # If there's a movie running, let it play if ($self->get_status() == PLAYER_STATUS_PLAY) { if ($heap->{'playback_type'} == PLAYBACK_TYPE_MOVIE) { $self->{'logger'}->warn("Attempted to play '$song_file' while a movie is playing"); $callback->($self->{'stream'}, PLAYBACK_ERROR); return; } else { $heap->{'stream_queue'}->set_stop_handler($callback); } } else { $self->{'logger'}->debug("Playing music file '$song_file'"); $heap->{'stream_queue'}->clear_events(); $self->{'stream'}->open($song_file) or do { $self->{'logger'}->warn("Unable to play '$song_file'"); $callback->($self->{'stream'}, PLAYBACK_ERROR); return; }; $self->{'stream'}->play(0,0); $heap->{'stream_queue'}->set_stop_handler($callback); $heap->{'stream_queue'}->spawn(); $heap->{'playback_type'} = PLAYBACK_TYPE_MUSIC; } } ############################## Object Methods ################################ ## ## spawn() ## ## Creates the appropriate Player session. ## sub spawn { my $self = shift; POE::Session->create( object_states => [ $self => [ qw(_start play play_still play_music stop ) ] , ], ); } ## ## get_status() ## ## Returns one of: ## PLAYER_STATUS_PLAY if a movie (or music) is playing ## PLAYER_STATUS_STOP if nothing is playing. ## sub get_status { my $self = shift; my $session = $poe_kernel->get_active_session(); my $heap = $session->get_heap(); if (! defined $self->{'stream'} ) { $self->{'logger'}->fatal("Undefined stream! Called on session $session, caller " . join(' ', caller()) ); confess("Undefined stream!"); } $self->{'stream'}->get_status() == XINE_STATUS_PLAY and return PLAYER_STATUS_PLAY; return PLAYER_STATUS_STOP; } package Video::PlaybackMachine::Player::EventWheel; # TODO: Make a subclass of EventWheel ### ### When spawned, these will pass along events from the given ### streams to the appropriate callbacks. ### use strict; use POE; use Video::Xine; ## How often to check to see if Xine has stopped, in seconds use constant XINE_CHECK_INTERVAL_SECS => 2; sub new { my $type = shift; my ($stream, %handlers) = @_; my $self = { type => $type, stream => $stream, handlers => { %handlers }, queue => undef, logger => Log::Log4perl->get_logger('Video.PlaybackMachine.Player.EventWheel'), }; $self->{queue} = Video::Xine::Event::Queue->new($self->{'stream'}) or die "Couldn't create Xine::Event::Queue"; bless $self, $type; } sub spawn { my $self = shift; my ($callback) = @_; POE::Session->create( object_states => [$self=>[qw(_start get_events)]] ); } sub _start { my ($self, $heap, $kernel) = @_[OBJECT, HEAP, KERNEL]; $kernel->yield('get_events'); } sub clear_events { my $self = shift; 1 while $self->{queue}->get_event(); } sub get_events { my ($self, $heap, $kernel) = @_[OBJECT, HEAP, KERNEL]; # Translate all events into callbacks while ( my $event = $self->{queue}->get_event() ) { $self->{'logger'}->debug("Received event: ", $event->get_type(), "\n"); if ( $event->get_type() == XINE_EVENT_UI_PLAYBACK_FINISHED ) { $self->{'stream'}->close(); } if ( exists $self->{'handlers'}{$event->get_type()} ) { $self->{'logger'}->debug("Invoking handler for ", $event->get_type(), "\n"); $self->{'handlers'}{$event->get_type()}->($self->{'stream'}, $event); } } # Keep checking so long as we're playing if ( $self->{'stream'}->get_status() == XINE_STATUS_PLAY ) { $kernel->delay('get_events', XINE_CHECK_INTERVAL_SECS); } } sub set_handler { my $self = shift; my ($event, $callback) = @_; $self->{'handlers'}{$event} = sub { $callback->($_[0], Video::PlaybackMachine::Player::PLAYBACK_OK) }; } # Convenience method sub set_stop_handler { $_[0]->set_handler(XINE_EVENT_UI_PLAYBACK_FINISHED, $_[1]); }