1

I'm trying to make a script that communicates with mplayer using open3, but the mplayer process is showing up as defunct and I am unable to send standard input into mplayer.

Here's the code:

#!/usr/bin/env perl

{
    package mplayer::test;
    use IPC::Open3;

    sub new {
        my $class = shift;
        my $self = bless { @_ }, $class;
        $self->start_mplayer();
        $self;
    }

    sub start_mplayer{
        my $self = shift;
        local *DEVNULL;
        open DEVNULL, ">/dev/null" or die "/dev/null: $!";
        open OUTPUT, ">out.log" or die "out.log: $!";
        $self->{r} = local *MPLAYER_READ;
        $self->{w} = local *MPLAYER_WRITE;
        $self->{pid} = open3($self->{w},$self->{r},">&DEVNULL",'mplayer -slave -idle -v');
        die "Error opening mplayer!\n" unless $self->{pid};
    }
    sub do{
        my ($self, $command) = @_;
        print {$self->{w}} $command, "\n";
    }
}

mplayer::test->new;

mplayer::test->do(qq~loadfile test.mp3~);
sleep(5);

I must be missing something obvious, I'm learning open3 from examples from other modules.

1 Answers1

1

First off, switch to lexical filehandles. Typeglobs are package global and difficult to work with.

One problem is with local *DEVNULL. You've made *DEVNULL local to start_mplayer (and whatever it calls, including open3), but then used the associated filehandles outside start_mplayer. By that time, *DEVNULL has reverted back to its global state (ie. empty) and open3 tries to write to an empty filehandle. You should have gotten a print() on unopened filehandle DEVNULL warning, but you don't have warnings on...

Solution: don't localize it. Unfortunately this means you can't have multiple mplayer instances running at once. Normally you'd solve this by using a lexical filehandle, but unfortunately the special >& syntax only works with glob handles. The solution is to only open DEVNULL once.

Alternatively you can let open3 write to an error filehandle and just ignore them. Wastes a miniscule amount of memory.

Other changes...

  • Turn on strict and warnings
  • OUTPUT is never used.
  • Breaking up the command into multiple args avoids possible shell interference.
  • Putting localized filehandles into the object beforehand is unnecessary.
  • autodie is easier than typing "or die ..." all the time.

Here's your reworked start_mplayer routine. I don't have a copy of mplayer to try it with, but it works fine with cat.

use strict;
use warnings;
use autodie;

sub start_mplayer{
    my $self = shift;

    # Only open DEVNULL once, since its going to be shared.
    open DEVNULL, ">", /dev/null" unless fileno DEVNULL;

    $self->{pid} = open3($self->{r}, $self->{w}, ">&DEVNULL", 'mplayer', '-slave', '-idle', '-v');

    die "Error opening mplayer!\n" unless $self->{pid};
}

To determine if its your program or something weird about mplayer, try a different command, like 'cat'. Often you have to close the input, or make sure it sees a newline, before a program will produce output.

For a more robust way to interact with programs, see IPC::Run.

Schwern
  • 153,029
  • 25
  • 195
  • 336