Exemple #1
0
int win32_fifo_mkfifo(const char *path)
{
	HANDLE ret;
	win32_fifo_close();
#ifdef WANT_WIN32_UNICODE
	wchar_t *str;
	if(win32_utf8_wide(path,&str,NULL) == 0)
	{
		fprintf(stderr,"Cannot get FIFO name, likely out of memory\n");
		return -1;
	}
#if (DEBUG == 1)
	fwprintf(stderr,L"CreateNamedPipeW %ws\n", str);
#endif
	ret = CreateNamedPipeW(str,PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,1,255,255,0,NULL);
	free(str);
#else
#if (DEBUG == 1)
	fprintf(stderr,"CreateNamedPipeA %s\n", path);
#endif
	ret = CreateNamedPipeA(path,PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,1,255,255,0,NULL);
#endif /* WANT_WIN32_UNICODE */
	if(ret == INVALID_HANDLE_VALUE) return -1;

	fifohandle = ret;
	/* Wait for client */
	ConnectNamedPipe(fifohandle,&ov1);
	WaitForSingleObjectEx(fifohandle,INFINITE,TRUE);
	return 0;
}
Exemple #2
0
int control_generic (mpg123_handle *fr)
{
    struct timeval tv;
    fd_set fds;
    int n;

    /* ThOr */
    char alive = 1;
    char silent = 0;

    /* responses to stderr for frontends needing audio data from stdout */
    if (param.remote_err)
        outstream = stderr;
    else
        outstream = stdout;

#ifndef WIN32
    setlinebuf(outstream);
#else /* perhaps just use setvbuf as it's C89 */
    /*
    fprintf(outstream, "You are on Win32 and want to use the control interface... tough luck: We need a replacement for select on STDIN first.\n");
    return 0;
    setvbuf(outstream, (char*)NULL, _IOLBF, 0);
    */
#endif
    /* the command behaviour is different, so is the ID */
    /* now also with version for command availability */
    fprintf(outstream, "@R MPG123 (ThOr) v8\n");
#ifdef FIFO
    if(param.fifo)
    {
        if(param.fifo[0] == 0)
        {
            error("You wanted an empty FIFO name??");
            return 1;
        }
#ifndef WANT_WIN32_FIFO
        unlink(param.fifo);
        if(mkfifo(param.fifo, 0666) == -1)
        {
            error2("Failed to create FIFO at %s (%s)", param.fifo, strerror(errno));
            return 1;
        }
        debug("going to open named pipe ... blocking until someone gives command");
#endif /* WANT_WIN32_FIFO */
#ifdef WANT_WIN32_FIFO
        control_file = win32_fifo_mkfifo(param.fifo);
#else
        control_file = open(param.fifo,O_RDONLY);
#endif /* WANT_WIN32_FIFO */
        debug("opened");
    }
#endif

    while (alive)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 0;
        FD_ZERO(&fds);
        FD_SET(control_file, &fds);
        /* play frame if no command needs to be processed */
        if (mode == MODE_PLAYING) {
#ifdef WANT_WIN32_FIFO
            n = win32_fifo_read_peek(&tv);
#else
            n = select(32, &fds, NULL, NULL, &tv);
#endif
            if (n == 0) {
                if (!play_frame())
                {
                    /* When the track ended, user may want to keep it open (to seek back),
                       so there is a decision between stopping and pausing at the end. */
                    if(param.keep_open)
                    {
                        mode = MODE_PAUSED;
                        /* Hm, buffer should be stopped already, shouldn't it? */
                        if(param.usebuffer) out123_pause(ao);
                        generic_sendmsg("P 1");
                    }
                    else
                    {
                        mode = MODE_STOPPED;
                        close_track();
                        generic_sendmsg("P 0");
                    }
                    continue;
                }
                if (init) {
                    print_remote_header(fr);
                    init = 0;
                }
                if(silent == 0)
                {
                    generic_sendstat(fr);
                    if(mpg123_meta_check(fr) & MPG123_NEW_ICY)
                    {
                        char *meta;
                        if(mpg123_icy(fr, &meta) == MPG123_OK)
                            generic_sendmsg("I ICY-META: %s", meta != NULL ? meta : "<nil>");
                    }
                }
            }
        }
        else {
            /* wait for command */
            while (1) {
#ifdef WANT_WIN32_FIFO
                n = win32_fifo_read_peek(NULL);
#else
                n = select(32, &fds, NULL, NULL, NULL);
#endif
                if (n > 0)
                    break;
            }
        }

        /*  on error */
        if (n < 0) {
            fprintf(stderr, "Error waiting for command: %s\n", strerror(errno));
            return 1;
        }

        /* read & process commands */
        if (n > 0)
        {
            short int len = 1; /* length of buffer */
            char *cmd, *arg; /* variables for parsing, */
            char *comstr = NULL; /* gcc thinks that this could be used uninitialited... */
            char buf[REMOTE_BUFFER_SIZE];
            short int counter;
            char *next_comstr = buf; /* have it initialized for first command */

            /* read as much as possible, maybe multiple commands */
            /* When there is nothing to read (EOF) or even an error, it is the end */
#ifdef WANT_WIN32_FIFO
            len = win32_fifo_read(buf,REMOTE_BUFFER_SIZE);
#else
            len = read(control_file, buf, REMOTE_BUFFER_SIZE);
#endif
            if(len < 1)
            {
#ifdef FIFO
                if(len == 0 && param.fifo)
                {
                    debug("fifo ended... reopening");
#ifdef WANT_WIN32_FIFO
                    win32_fifo_mkfifo(param.fifo);
#else
                    close(control_file);
                    control_file = open(param.fifo,O_RDONLY|O_NONBLOCK);
#endif
                    if(control_file < 0) {
                        error1("open of fifo failed... %s", strerror(errno));
                        break;
                    }
                    continue;
                }
#endif
                if(len < 0) error1("command read error: %s", strerror(errno));
                break;
            }

            debug1("read %i bytes of commands", len);
            /* one command on a line - separation by \n -> C strings in a row */
            for(counter = 0; counter < len; ++counter)
            {
                /* line end is command end */
                if( (buf[counter] == '\n') || (buf[counter] == '\r') )
                {
                    debug1("line end at counter=%i", counter);
                    buf[counter] = 0; /* now it's a properly ending C string */
                    comstr = next_comstr;

                    /* skip the additional line ender of \r\n or \n\r */
                    if( (counter < (len - 1)) && ((buf[counter+1] == '\n') || (buf[counter+1] == '\r')) ) buf[++counter] = 0;

                    /* next "real" char is first of next command */
                    next_comstr = buf + counter+1;

                    /* directly process the command now */
                    debug1("interpreting command: %s", comstr);
                    if(strlen(comstr) == 0) continue;

                    /* PAUSE */
                    if (!strcasecmp(comstr, "P") || !strcasecmp(comstr, "PAUSE")) {
                        if(mode != MODE_STOPPED)
                        {
                            if (mode == MODE_PLAYING) {
                                mode = MODE_PAUSED;
                                out123_pause(ao);
                                generic_sendmsg("P 1");
                            } else {
                                mode = MODE_PLAYING;
                                out123_continue(ao);
                                generic_sendmsg("P 2");
                            }
                        } else generic_sendmsg("P 0");
                        continue;
                    }

                    /* STOP */
                    if (!strcasecmp(comstr, "S") || !strcasecmp(comstr, "STOP")) {
                        if (mode != MODE_STOPPED) {
                            /* Do we want to drop here? */
                            out123_drop(ao);
                            out123_stop(ao);
                            close_track();
                            mode = MODE_STOPPED;
                            generic_sendmsg("P 0");
                        } else generic_sendmsg("P 0");
                        continue;
                    }

                    /* SILENCE */
                    if(!strcasecmp(comstr, "SILENCE")) {
                        silent = 1;
                        generic_sendmsg("silence");
                        continue;
                    }

                    if(!strcasecmp(comstr, "T") || !strcasecmp(comstr, "TAG")) {
                        generic_sendalltag(fr);
                        continue;
                    }

                    if(!strcasecmp(comstr, "SCAN"))
                    {
                        if(mode != MODE_STOPPED)
                        {
                            if(mpg123_scan(fr) == MPG123_OK)
                                generic_sendmsg("SCAN done");
                            else
                                generic_sendmsg("E %s", mpg123_strerror(fr));
                        }
                        else generic_sendmsg("E No track loaded!");

                        continue;
                    }

                    if(!strcasecmp(comstr, "SAMPLE"))
                    {
                        off_t pos = mpg123_tell(fr);
                        off_t len = mpg123_length(fr);
                        /* I need to have portable printf specifiers that do not truncate the type... more autoconf... */
                        if(len < 0) generic_sendmsg("E %s", mpg123_strerror(fr));
                        else generic_sendmsg("SAMPLE %li %li", (long)pos, (long)len);
                        continue;
                    }

                    if(!strcasecmp(comstr, "FORMAT"))
                    {
                        long rate;
                        int ch;
                        int ret = mpg123_getformat(fr, &rate, &ch, NULL);
                        /* I need to have portable printf specifiers that do not truncate the type... more autoconf... */
                        if(ret < 0) generic_sendmsg("E %s", mpg123_strerror(fr));
                        else generic_sendmsg("FORMAT %li %i", rate, ch);
                        continue;
                    }

                    if(!strcasecmp(comstr, "SHOWEQ"))
                    {
                        int i;
                        generic_sendmsg("SHOWEQ {");
                        for(i=0; i<32; ++i)
                        {
                            generic_sendmsg("SHOWEQ %i : %i : %f", MPG123_LEFT, i, mpg123_geteq(fr, MPG123_LEFT, i));
                            generic_sendmsg("SHOWEQ %i : %i : %f", MPG123_RIGHT, i, mpg123_geteq(fr, MPG123_RIGHT, i));
                        }
                        generic_sendmsg("SHOWEQ }");
                        continue;
                    }

                    if(!strcasecmp(comstr, "STATE"))
                    {
                        long val;
                        generic_sendmsg("STATE {");
                        /* Get some state information bits and display them. */
                        if(mpg123_getstate(fr, MPG123_ACCURATE, &val, NULL) == MPG123_OK)
                            generic_sendmsg("STATE accurate %li", val);

                        generic_sendmsg("STATE }");
                        continue;
                    }

                    /* QUIT */
                    if (!strcasecmp(comstr, "Q") || !strcasecmp(comstr, "QUIT")) {
                        alive = FALSE;
                        continue;
                    }

                    /* some HELP */
                    if (!strcasecmp(comstr, "H") || !strcasecmp(comstr, "HELP")) {
                        generic_sendmsg("H {");
                        generic_sendmsg("H HELP/H: command listing (LONG/SHORT forms), command case insensitve");
                        generic_sendmsg("H LOAD/L <trackname>: load and start playing resource <trackname>");
                        generic_sendmsg("H LOADPAUSED/LP <trackname>: load but do not start playing resource <trackname>");
                        generic_sendmsg("H LOADLIST/LL <entry> <url>: load a playlist from given <url>, and display its entries, optionally load and play one of these specificed by the integer <entry> (<0: just list, 0: play last track, >0:play track with that position in list)");
                        generic_sendmsg("H PAUSE/P: pause playback");
                        generic_sendmsg("H STOP/S: stop playback (closes file)");
                        generic_sendmsg("H JUMP/J <frame>|<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame <frame> or change position by offset, same in seconds if number followed by \"s\"");
                        generic_sendmsg("H VOLUME/V <percent>: set volume in % (0..100...); float value");
                        generic_sendmsg("H RVA off|(mix|radio)|(album|audiophile): set rva mode");
                        generic_sendmsg("H EQ/E <channel> <band> <value>: set equalizer value for frequency band 0 to 31 on channel %i (left) or %i (right) or %i (both)", MPG123_LEFT, MPG123_RIGHT, MPG123_LR);
                        generic_sendmsg("H EQFILE <filename>: load EQ settings from a file");
                        generic_sendmsg("H SHOWEQ: show all equalizer settings (as <channel> <band> <value> lines in a SHOWEQ block (like TAG))");
                        generic_sendmsg("H SEEK/K <sample>|<+offset>|<-offset>: jump to output sample position <samples> or change position by offset");
                        generic_sendmsg("H SCAN: scan through the file, building seek index");
                        generic_sendmsg("H SAMPLE: print out the sample position and total number of samples");
                        generic_sendmsg("H FORMAT: print out sampling rate in Hz and channel count");
                        generic_sendmsg("H SEQ <bass> <mid> <treble>: simple eq setting...");
                        generic_sendmsg("H PITCH <[+|-]value>: adjust playback speed (+0.01 is 1 %% faster)");
                        generic_sendmsg("H SILENCE: be silent during playback (meaning silence in text form)");
                        generic_sendmsg("H STATE: Print auxiliary state info in several lines (just try it to see what info is there).");
                        generic_sendmsg("H TAG/T: Print all available (ID3) tag info, for ID3v2 that gives output of all collected text fields, using the ID3v2.3/4 4-character names. NOTE: ID3v2 data will be deleted on non-forward seeks.");
                        generic_sendmsg("H    The output is multiple lines, begin marked by \"@T {\", end by \"@T }\".");
                        generic_sendmsg("H    ID3v1 data is like in the @I info lines (see below), just with \"@T\" in front.");
                        generic_sendmsg("H    An ID3v2 data field is introduced via ([ ... ] means optional):");
                        generic_sendmsg("H     @T ID3v2.<NAME>[ [lang(<LANG>)] desc(<description>)]:");
                        generic_sendmsg("H    The lines of data follow with \"=\" prefixed:");
                        generic_sendmsg("H     @T =<one line of content in UTF-8 encoding>");
                        generic_sendmsg("H meaning of the @S stream info:");
                        generic_sendmsg("H %s", remote_header_help);
                        generic_sendmsg("H The @I lines after loading a track give some ID3 info, the format:");
                        generic_sendmsg("H      @I ID3:artist  album  year  comment genretext");
                        generic_sendmsg("H     where artist,album and comment are exactly 30 characters each, year is 4 characters, genre text unspecified.");
                        generic_sendmsg("H     You will encounter \"@I ID3.genre:<number>\" and \"@I ID3.track:<number>\".");
                        generic_sendmsg("H     Then, there is an excerpt of ID3v2 info in the structure");
                        generic_sendmsg("H      @I ID3v2.title:Blabla bla Bla");
                        generic_sendmsg("H     for every line of the \"title\" data field. Likewise for other fields (author, album, etc).");
                        generic_sendmsg("H }");
                        continue;
                    }

                    /* commands with arguments */
                    cmd = NULL;
                    arg = NULL;
                    cmd = strtok(comstr," \t"); /* get the main command */
                    arg = strtok(NULL,""); /* get the args */

                    if (cmd && strlen(cmd) && arg && strlen(arg))
                    {
#ifndef NO_EQUALIZER
                        /* Simple EQ: SEQ <BASS> <MID> <TREBLE>  */
                        if (!strcasecmp(cmd, "SEQ")) {
                            double b,m,t;
                            int cn;
                            if(sscanf(arg, "%lf %lf %lf", &b, &m, &t) == 3)
                            {
                                /* Consider adding mpg123_seq()... but also, on could define a nicer courve for that. */
                                if ((t >= 0) && (t <= 3))
                                    for(cn=0; cn < 1; ++cn)	mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, b);

                                if ((m >= 0) && (m <= 3))
                                    for(cn=1; cn < 2; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, m);

                                if ((b >= 0) && (b <= 3))
                                    for(cn=2; cn < 32; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, t);

                                generic_sendmsg("bass: %f mid: %f treble: %f", b, m, t);
                            }
                            else generic_sendmsg("E invalid arguments for SEQ: %s", arg);
                            continue;
                        }

                        /* Equalizer control :) (JMG) */
                        if (!strcasecmp(cmd, "E") || !strcasecmp(cmd, "EQ")) {
                            double e; /* ThOr: equalizer is of type real... whatever that is */
                            int c, v;
                            /*generic_sendmsg("%s",updown);*/
                            if(sscanf(arg, "%i %i %lf", &c, &v, &e) == 3)
                            {
                                if(mpg123_eq(fr, c, v, e) == MPG123_OK)
                                    generic_sendmsg("%i : %i : %f", c, v, e);
                                else
                                    generic_sendmsg("E failed to set eq: %s", mpg123_strerror(fr));
                            }
                            else generic_sendmsg("E invalid arguments for EQ: %s", arg);
                            continue;
                        }

                        if(!strcasecmp(cmd, "EQFILE"))
                        {
                            equalfile = arg;
                            if(load_equalizer(fr) == 0)
                                generic_sendmsg("EQFILE done");
                            else
                                generic_sendmsg("E failed to parse given eq file");

                            continue;
                        }
#endif
                        /* SEEK to a sample offset */
                        if(!strcasecmp(cmd, "K") || !strcasecmp(cmd, "SEEK"))
                        {
                            off_t soff;
                            off_t oldpos;
                            off_t newpos;
                            char *spos = arg;
                            int whence = SEEK_SET;
                            if(mode == MODE_STOPPED)
                            {
                                generic_sendmsg("E No track loaded!");
                                continue;
                            }
                            oldpos = mpg123_tell(fr);

                            soff = (off_t) atobigint(spos);
                            if(spos[0] == '-' || spos[0] == '+') whence = SEEK_CUR;
                            if(0 > (soff = mpg123_seek(fr, soff, whence)))
                            {
                                generic_sendmsg("E Error while seeking: %s", mpg123_strerror(fr));
                                mpg123_seek(fr, 0, SEEK_SET);
                            }
                            out123_drop(ao);

                            newpos = mpg123_tell(fr);
                            if(newpos <= oldpos) mpg123_meta_free(fr);

                            generic_sendmsg("K %"OFF_P, (off_p)newpos);
                            continue;
                        }
                        /* JUMP */
                        if (!strcasecmp(cmd, "J") || !strcasecmp(cmd, "JUMP")) {
                            char *spos;
                            off_t offset;
                            off_t oldpos;
                            double secs;

                            spos = arg;
                            if(mode == MODE_STOPPED)
                            {
                                generic_sendmsg("E No track loaded!");
                                continue;
                            }
                            oldpos = framenum;

                            if(spos[strlen(spos)-1] == 's' && sscanf(arg, "%lf", &secs) == 1) offset = mpg123_timeframe(fr, secs);
                            else offset = atol(spos);
                            /* totally replaced that stuff - it never fully worked
                               a bit usure about why +pos -> spos+1 earlier... */
                            if (spos[0] == '-' || spos[0] == '+') offset += framenum;

                            if(0 > (framenum = mpg123_seek_frame(fr, offset, SEEK_SET)))
                            {
                                generic_sendmsg("E Error while seeking");
                                mpg123_seek_frame(fr, 0, SEEK_SET);
                            }
                            out123_drop(ao);

                            if(framenum <= oldpos) mpg123_meta_free(fr);
                            generic_sendmsg("J %d", framenum);
                            continue;
                        }

                        /* VOLUME in percent */
                        if(!strcasecmp(cmd, "V") || !strcasecmp(cmd, "VOLUME"))
                        {
                            double v;
                            mpg123_volume(fr, atof(arg)/100);
                            mpg123_getvolume(fr, &v, NULL, NULL); /* Necessary? */
                            generic_sendmsg("V %f%%", v * 100);
                            continue;
                        }

                        /* PITCH (playback speed) in percent */
                        if(!strcasecmp(cmd, "PITCH"))
                        {
                            double p;
                            if(sscanf(arg, "%lf", &p) == 1)
                            {
                                set_pitch(fr, ao, p);
                                generic_sendmsg("PITCH %f", param.pitch);
                            }
                            else generic_sendmsg("E invalid arguments for PITCH: %s", arg);
                            continue;
                        }

                        /* RVA mode */
                        if(!strcasecmp(cmd, "RVA"))
                        {
                            if(!strcasecmp(arg, "off")) param.rva = MPG123_RVA_OFF;
                            else if(!strcasecmp(arg, "mix") || !strcasecmp(arg, "radio")) param.rva = MPG123_RVA_MIX;
                            else if(!strcasecmp(arg, "album") || !strcasecmp(arg, "audiophile")) param.rva = MPG123_RVA_ALBUM;
                            mpg123_volume_change(fr, 0.);
                            generic_sendmsg("RVA %s", rva_name[param.rva]);
                            continue;
                        }

                        /* LOAD - actually play */
                        if (!strcasecmp(cmd, "L") || !strcasecmp(cmd, "LOAD")) {
                            generic_load(fr, arg, MODE_PLAYING);
                            continue;
                        }

                        if (!strcasecmp(cmd, "LL") || !strcasecmp(cmd, "LOADLIST")) {
                            generic_loadlist(fr, arg);
                            continue;
                        }

                        /* LOADPAUSED */
                        if (!strcasecmp(cmd, "LP") || !strcasecmp(cmd, "LOADPAUSED")) {
                            generic_load(fr, arg, MODE_PAUSED);
                            continue;
                        }

                        /* no command matched */
                        generic_sendmsg("E Unknown command: %s", cmd); /* unknown command */
                    } /* end commands with arguments */
                    else generic_sendmsg("E Unknown command or no arguments: %s", comstr); /* unknown command */

                } /* end of single command processing */
            } /* end of scanning the command buffer */

            /*
               when last command had no \n... should I discard it?
               Ideally, I should remember the part and wait for next
            	 read() to get the rest up to a \n. But that can go
            	 to infinity. Too long commands too quickly are just
            	 bad. Cannot/Won't change that. So, discard the unfinished
            	 command and have fingers crossed that the rest of this
            	 unfinished one qualifies as "unknown".
            */
            if(buf[len-1] != 0)
            {
                char lasti = buf[len-1];
                buf[len-1] = 0;
                generic_sendmsg("E Unfinished command: %s%c", comstr, lasti);
            }
        } /* end command reading & processing */
    } /* end main (alive) loop */
    debug("going to end");
    /* quit gracefully */
    debug("closing control");
#ifdef FIFO
#if WANT_WIN32_FIFO
    win32_fifo_close();
#else
    close(control_file); /* be it FIFO or STDIN */
    if(param.fifo) unlink(param.fifo);
#endif /* WANT_WIN32_FIFO */
#endif
    debug("control_generic returning");
    return 0;
}