**VLC Tcl Extension** [bll] 2016-12-28 : One full day's work. This is a Tcl interface to libvlc, which comes with the VLC media player. The tighter interface gives better control over song position, time position and player state. In addition to some of the basic controls, I implemented a callback registration that will call a tcl procedure upon entering a particular player state. I only need audio, and don't use playlists. The media sub-command only handles file paths right now. There's quite a bit that could still be implemented. But most of that implementation would be fairly straightforward. It seems to be working on Linux and Mac OS X. It was crashing on Windows, but I believe that is fixed now (I certainly don't understand these reference count problems). '''test.tcl''' ====== #!/usr/bin/tclsh set os $::tcl_platform(os) if { [regexp -nocase {^win} $os] } { set os Windows } load [pwd]/$os/tclvlc64[info sharedlibextension] tclvlc init --intf dummy --no-video --ignore-config --no-loop --no-random --no-repeat --quiet --play-and-stop if { $os eq "Linux" } { tclvlc media /home/bll/sources/ballroomdj/test.dir/test-files/counter.mp3 } if { $os eq "Darwin" } { tclvlc media /Users/bll/Desktop/BallroomDJ.app/Contents/MacOS/test.dir/test-files/counter.mp3 } if { $os eq "Windows" } { tclvlc media [file nativename {C:/Users/bll/Desktop/BallroomDJ/test.dir/test-files/counter.mp3}] } proc doplay { a } { puts "play... ($a)" } tclvlc callback playing {doplay hello} tclvlc seek 0.0387 tclvlc play set ::x 0 after 2000 set ::x 1 vwait ::x tclvlc stop tclvlc close exit ====== '''tclvlc.c''' ====== /* * Copyright 2016-2017 Brad Lanam Walnut Creek CA US * MIT License */ #include #include #include #include typedef struct { char *name; Tcl_ObjCmdProc *proc; } EnsembleData; typedef struct { Tcl_ObjCmdProc *cmd; Tcl_Obj *obj; } cmdStack_t; #define CMDSTACKMAX 5 typedef struct { libvlc_state_t state; const char * name; } stateMap_t; static const stateMap_t stateMap[] = { { libvlc_NothingSpecial, "idle" }, { libvlc_Opening, "opening" }, { libvlc_Buffering, "buffering" }, { libvlc_Playing, "playing" }, { libvlc_Paused, "paused" }, { libvlc_Stopped, "stopped" }, { libvlc_Ended, "ended" }, { libvlc_Error, "error" } }; #define stateMapMax (sizeof(stateMap)/sizeof(stateMap_t)) typedef struct { Tcl_Interp *interp; libvlc_instance_t *inst; const char *version; libvlc_media_player_t *mp; libvlc_state_t state; int argc; const char **argv; cmdStack_t cmdStack [CMDSTACKMAX]; int stacksize; Tcl_Obj *callback[stateMapMax]; } vlcData_t; const char * stateToStr ( libvlc_state_t state ) { int i; const char *tptr; tptr = ""; for (i = 0; i < stateMapMax; ++i) { if (state == stateMap[i].state) { tptr = stateMap[i].name; break; } } return tptr; } libvlc_state_t stateToValue ( char *name ) { int i; libvlc_state_t state; state = libvlc_NothingSpecial; for (i = 0; i < stateMapMax; ++i) { if (strcmp (name, stateMap[i].name) == 0) { state = stateMap[i].state; break; } } return state; } void vlcPushStack ( vlcData_t *vlcData, Tcl_ObjCmdProc *proc, int objc, Tcl_Obj * const * objv ) { int i; if (vlcData->stacksize >= CMDSTACKMAX) { return; } vlcData->cmdStack[vlcData->stacksize].cmd = proc; vlcData->cmdStack[vlcData->stacksize].obj = Tcl_NewListObj (objc, objv); ++vlcData->stacksize; } void vlcExecStack ( vlcData_t *vlcData ) { int i; int j; Tcl_ObjCmdProc *proc; int objc; Tcl_Obj **objv; for (i = 0; i < vlcData->stacksize; ++i) { proc = vlcData->cmdStack[i].cmd; Tcl_ListObjGetElements (vlcData->interp, vlcData->cmdStack[i].obj, &objc, &objv); (*proc) ((ClientData) vlcData, vlcData->interp, objc, objv); } vlcData->stacksize = 0; } void vlcEventHandler ( const struct libvlc_event_t *event, void *cd ) { vlcData_t *vlcData = (vlcData_t *) cd; if (event->type == libvlc_MediaStateChanged) { vlcData->state = event->u.media_state_changed.new_state; if (vlcData->state == libvlc_Playing) { vlcExecStack (vlcData); } if (vlcData->callback[vlcData->state] != NULL) { Tcl_EvalObjEx (vlcData->interp, vlcData->callback[vlcData->state], 0); } } } int vlcCallbackCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { libvlc_state_t state; vlcData_t *vlcData = (vlcData_t *) cd; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "state ?proc? ?args?"); return TCL_ERROR; } state = stateToValue (Tcl_GetString(objv[1])); if (objc > 2) { vlcData->callback[state] = Tcl_NewListObj (1, objv + 2); Tcl_IncrRefCount (vlcData->callback[state]); } else if (vlcData->callback[state] != NULL) { Tcl_DecrRefCount (vlcData->callback[state]); vlcData->callback[state] = NULL; } return TCL_OK; } int vlcDurationCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; libvlc_time_t tm; vlcData_t *vlcData = (vlcData_t *) cd; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { tm = libvlc_media_player_get_length (vlcData->mp); Tcl_SetObjResult (interp, Tcl_NewLongObj (tm)); } return rc; } int vlcGetTimeCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; libvlc_time_t tm; vlcData_t *vlcData = (vlcData_t *) cd; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { tm = libvlc_media_player_get_time (vlcData->mp); Tcl_SetObjResult (interp, Tcl_NewLongObj (tm)); } return rc; } int vlcMediaCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; libvlc_media_t *m; libvlc_event_manager_t *em; vlcData_t *vlcData = (vlcData_t *) cd; char *fn; struct stat statinfo; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "mediapath"); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { fn = Tcl_GetString(objv[1]); if (stat (fn, &statinfo) != 0) { rc = TCL_ERROR; return rc; } m = libvlc_media_new_path (vlcData->inst, fn); libvlc_media_player_set_rate (vlcData->mp, 1.0); em = libvlc_media_event_manager (m); libvlc_event_attach (em, libvlc_MediaStateChanged, &vlcEventHandler, vlcData); libvlc_media_player_set_media (vlcData->mp, m); libvlc_media_release (m); } return rc; } int vlcPauseCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; vlcData_t *vlcData = (vlcData_t *) cd; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { if (vlcData->state == libvlc_Opening || vlcData->state == libvlc_Buffering) { ; } else if (vlcData->state == libvlc_Playing) { libvlc_media_player_set_pause (vlcData->mp, 1); } else if (vlcData->state == libvlc_Paused) { libvlc_media_player_set_pause (vlcData->mp, 0); } } return rc; } int vlcPlayCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; vlcData_t *vlcData = (vlcData_t *) cd; if (objc != 1) { Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { libvlc_media_player_play (vlcData->mp); } return rc; } int vlcRateCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; vlcData_t *vlcData = (vlcData_t *) cd; float rate; double d; if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?rate?"); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { if (objc == 2) { if (vlcData->state == libvlc_Playing) { rc = Tcl_GetDoubleFromObj (interp, objv[1], &d); if (rc == TCL_OK) { rate = (float) d; libvlc_media_player_set_rate (vlcData->mp, rate); } } else { vlcPushStack (vlcData, &vlcRateCmd, objc, objv); } } rate = libvlc_media_player_get_rate (vlcData->mp); Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) rate)); } return rc; } int vlcSeekCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; vlcData_t *vlcData = (vlcData_t *) cd; float pos; double d; if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "?position?"); return TCL_ERROR; } rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { if (objc == 2) { if (vlcData->state == libvlc_Playing) { rc = Tcl_GetDoubleFromObj (interp, objv[1], &d); if (rc == TCL_OK) { pos = (float) d; libvlc_media_player_set_position (vlcData->mp, pos); } } else { vlcPushStack (vlcData, &vlcSeekCmd, objc, objv); } } pos = libvlc_media_player_get_position (vlcData->mp); Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) pos)); } return rc; } int vlcStateCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; libvlc_state_t plstate; vlcData_t *vlcData = (vlcData_t *) cd; rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { plstate = vlcData->state; Tcl_SetObjResult (interp, Tcl_NewStringObj (stateToStr(plstate), -1)); } return rc; } int vlcStopCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { int rc; vlcData_t *vlcData = (vlcData_t *) cd; rc = TCL_OK; if (vlcData->inst == NULL || vlcData->mp == NULL) { rc = TCL_ERROR; } else { libvlc_media_player_stop (vlcData->mp); } return rc; } void vlcClose ( vlcData_t *vlcData ) { int i; for (i = 0; i < stateMapMax; ++i) { if (vlcData->callback[i] != NULL) { Tcl_DecrRefCount (vlcData->callback[i]); } vlcData->callback[i] = NULL; } if (vlcData->mp != NULL) { libvlc_media_player_stop (vlcData->mp); libvlc_media_player_release (vlcData->mp); vlcData->mp = NULL; } if (vlcData->inst != NULL) { libvlc_release (vlcData->inst); vlcData->inst = NULL; } if (vlcData->argv != NULL) { for (i = 0; i < vlcData->argc; ++i) { ckfree (vlcData->argv[i]); } ckfree (vlcData->argv); vlcData->argv = NULL; } vlcData->state = libvlc_NothingSpecial; vlcData->stacksize = 0; } void vlcExitHandler ( void *cd ) { vlcData_t *vlcData = (vlcData_t *) cd; vlcClose (vlcData); ckfree (cd); } int vlcReleaseCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { vlcData_t *vlcData = (vlcData_t *) cd; vlcClose (vlcData); return TCL_OK; } int vlcInitCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { char *tptr; char *nptr; int rc; int i; int len; vlcData_t *vlcData = (vlcData_t *) cd; vlcData->argv = (const char **) ckalloc (sizeof(const char *) * (size_t) (objc + 1)); for (i = 0; i < objc; ++i) { tptr = Tcl_GetStringFromObj (objv[i], &len); nptr = (char *) ckalloc (len+1); strcpy (nptr, tptr); vlcData->argv[i] = nptr; } vlcData->argc = objc; vlcData->argv[objc] = NULL; rc = TCL_ERROR; vlcData->version = libvlc_get_version (); if (vlcData->inst == NULL) { vlcData->inst = libvlc_new (objc, vlcData->argv); } if (vlcData->inst != NULL && vlcData->mp == NULL) { vlcData->mp = libvlc_media_player_new (vlcData->inst); } if (vlcData->inst != NULL && vlcData->mp != NULL) { rc = TCL_OK; libvlc_audio_set_volume (vlcData->mp, 100); Tcl_CreateExitHandler (vlcExitHandler, cd); } return rc; } static const EnsembleData vlcCmdMap[] = { { "callback", vlcCallbackCmd }, { "close", vlcReleaseCmd }, { "duration", vlcDurationCmd }, { "gettime", vlcGetTimeCmd }, { "init", vlcInitCmd }, { "media", vlcMediaCmd }, { "pause", vlcPauseCmd }, { "play", vlcPlayCmd }, { "rate", vlcRateCmd }, { "seek", vlcSeekCmd }, { "state", vlcStateCmd }, { "stop", vlcStopCmd }, { NULL, NULL } }; int Tclvlc_Init (Tcl_Interp *interp) { Tcl_Namespace *nsPtr = NULL; Tcl_Command ensemble = NULL; Tcl_Obj *dictObj = NULL; Tcl_DString ds; vlcData_t *vlcData; int i; const char *nsName = "::tcl::tclvlc"; const char *cmdName = nsName + 5; #ifdef USE_TCL_STUBS if (!Tcl_InitStubs (interp,"8.3",0)) { return TCL_ERROR; } #else if (!Tcl_PkgRequire (interp,"Tcl","8.3",0)) { return TCL_ERROR; } #endif vlcData = (vlcData_t *) ckalloc (sizeof (vlcData_t)); vlcData->interp = interp; vlcData->inst = NULL; vlcData->mp = NULL; vlcData->argv = NULL; vlcData->state = libvlc_NothingSpecial; vlcData->stacksize = 0; for (i = 0; i < stateMapMax; ++i) { vlcData->callback[i] = NULL; } nsPtr = Tcl_CreateNamespace(interp, nsName, NULL, 0); if (nsPtr == NULL) { Tcl_Panic ("failed to create namespace: %s\n", nsName); } ensemble = Tcl_CreateEnsemble(interp, cmdName, nsPtr, TCL_ENSEMBLE_PREFIX); if (ensemble == NULL) { Tcl_Panic ("failed to create ensemble: %s\n", cmdName); } Tcl_DStringInit (&ds); Tcl_DStringAppend (&ds, nsName, -1); dictObj = Tcl_NewObj(); for (i = 0; vlcCmdMap[i].name != NULL; ++i) { Tcl_Obj *nameObj; Tcl_Obj *fqdnObj; nameObj = Tcl_NewStringObj (vlcCmdMap[i].name, -1); fqdnObj = Tcl_NewStringObj (Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); Tcl_AppendStringsToObj (fqdnObj, "::", vlcCmdMap[i].name, NULL); Tcl_DictObjPut (NULL, dictObj, nameObj, fqdnObj); if (vlcCmdMap[i].proc) { Tcl_CreateObjCommand (interp, Tcl_GetString (fqdnObj), vlcCmdMap[i].proc, (ClientData) vlcData, NULL); } } if (ensemble) { Tcl_SetEnsembleMappingDict (interp, ensemble, dictObj); } Tcl_DStringFree(&ds); Tcl_PkgProvide (interp, cmdName+2, "0.1"); return TCL_OK; } ====== '''mkvlc.sh''' ====== #!/bin/sh tv=8.6 slext=.so os=`uname -s` arch=`uname -m` bits=64 case $arch in i?86*) bits=32 ;; esac inc=-I/usr/include/tcl${tv} lib= lv=$tv if [ $os = "Darwin" ]; then slext=.dylib # for MacPorts tcl inc=-I/opt/local/include lib=-L/opt/local/lib inc+=" -I/Applications/VLC.app/Contents/MacOS/include" lib+=" -L/Applications/VLC.app/Contents/MacOS/lib" fi ${CC:-cc} -O -shared -fPIC -o tclvlc${slext} $inc -DUSE_TCL_STUBS tclvlc.c $lib -ltclstub${lv} -lvlc test -d $os || mkdir $os if [ -f tclvlc${slext} ]; then echo "tclvlc success" mv -f tclvlc${slext} $os/tclvlc${bits}${slext} fi if [ $os = "Darwin" ]; then install_name_tool -change "@loader_path/lib/libvlc.5.dylib" "/Applications/VLC.app/Contents/MacOS/lib/libvlc.5.dylib" Darwin/* fi ====== '''winmkvlc.sh''' ====== #!/bin/bash test -d Windows || mkdir Windows case $MSYSTEM in *32) gcc -m32 -shared -static-libgcc -DWIN_TCL_INTERFACE -o Windows/tclvlc32.dll \ -I'/home/bll/vlc/vlc-2.2.4/include' \ '-Wl,-rpath=/c/Program Files/VideoLAN/vlc' \ -I$HOME/local-32/include -DUSE_TCL_STUBS tclvlc.c \ -L$HOME/local-32/lib -ltclstub86 \ -L'/c/Program Files/VideoLAN/vlc' -lvlc ;; *64) gcc -m64 -shared -static-libgcc -DWIN_TCL_INTERFACE -o Windows/tclvlc64.dll \ -I'/home/bll/vlc/vlc-2.2.4/include' \ '-Wl,-rpath=/c/Program Files/VideoLAN/vlc' \ -I$HOME/local-64/include -DUSE_TCL_STUBS tclvlc.c \ -L$HOME/local-64/lib -ltclstub86 \ -L'/c/Program Files/VideoLAN/vlc' -lvlc ;; esac ====== <>Package | Multimedia