**MPV Tcl Extension**
[bll] 2018-3-1 This is a Tcl interface to the MPV audio player, using libmpv.
Linux users should be able to readily install libmpv.
Windows users can get libmpv from: https://sourceforge.net/projects/mpv-player-windows/files/libmpv/ .
I have not found a pre-compiled libmpv for Mac OS.
Like the [VLC Tcl Extension], I only need audio, and don't use playlists.
This code has not been extensively tested, I don't yet know how robust it is.
At this time, it has only been tested on Linux and Windows 7 (64-bit).
<<discussion>>Change Log[bll] 2018-4-16 Internal state on seek is still playing. Update state table.
Reinitialize duration and time on media load.
[bll] 2018-3-30 Update: change provided version number to 1.1 rather than using the MPV version number.<<enddiscussion>>
See Also: [VLC Tcl Extension]
'''tclmpv.c'''
======
/*
* Copyright 2018 Brad Lanam Walnut Creek CA US
*/
#define MPVDEBUG 0
#define USE_TCL_STUBS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tcl.h>
#include <mpv/client.h>
#define CHKTIMER 10
typedef struct { char *name; Tcl_ObjCmdProc *proc; } EnsembleData;
typedef enum playstate {
PS_NONE = 0,
PS_IDLE = 1,
PS_OPENING = 2, /* for MPV, mark the state as buffering until we receive the */
/* duration property change */
PS_BUFFERING = 3,
PS_PLAYING = 34,
PS_PAUSED = 45,
PS_STOPPED = 56,
PS_ERROR = 67
} playstate;
typedef struct {
playstate state;
const char * name;
} playStateMap_t;
static const playStateMap_t playStateMap[] = {
{ PS_NONE, "none" },
{ PS_IDLE, "idle" },
{ PS_OPENING, "opening" }, { PS_BUFFERING, "buffering" },
{ PS_PLAYING, "playing" },
{ PS_PAUSED, "paused" },
{ PS_STOPPED, "stopped" },
{ PS_ERROR, "error" }
};
#define playStateMapMax (sizeof(playStateMap)/sizeof(playStateMap_t))
typedef struct {
mpv_event_id state;
const char * name;
playstate stateflag;
} stateMap_t;
static const stateMap_t stateMap[] = {
{ MPV_EVENT_NONE, "none", PS_NONE },
{ MPV_EVENT_IDLE, "idle", PS_IDLE },
{ MPV_EVENT_START_FILE, "opening", PS_OPENING }, { MPV_EVENT_FILE_LOADED, "playing", PS_PLAYBUFFERING },
{ MPV_EVENT_PROPERTY_CHANGE, "property-chg", PS_NONE }, { MPV_EVENT_SEEK, "seeking", PS_PLAYINGONE },
{ MPV_EVENT_PLAYBACK_RESTART, "playafterseek", PS_PLAYINGONE },
{ MPV_EVENT_END_FILE, "stopped", PS_STOPPED }, { MPV_EVENT_SHUTDOWN, "ended", PS_STOPPED },
/* these next three are only for debugging */
{ MPV_EVENT_TRACKS_CHANGED, "tracks-changed", PS_NONE },
{ MPV_EVENT_AUDIO_RECONFIG, "audio-reconf", PS_NONE },
{ MPV_EVENT_METADATA_UPDATE, "metadata-upd", PS_NONE }
};
#define stateMapMax (sizeof(stateMap)/sizeof(stateMap_t))
#define stateMapIdxMax 40 /* mpv currently has 24 states coded */
typedef struct {
Tcl_Interp *interp;
mpv_handle *inst;
char version [40];
mpv_event_id state;
int argc;
const char **argv;
const char *device;
double duration;
double tm;
int paused;
int hasEvent; /* flag to process mpv event */
Tcl_TimerToken timerToken;
int stateMapIdx [stateMapIdxMax];
FILE *debugfh;
} mpvData_t;
const char *
mpvStateToStr (
mpv_event_id 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;
}
const char *
stateToStr (
playstate state
)
{
int i;
const char *tptr;
tptr = "";
for (i = 0; i < playStateMapMax; ++i) {
if (state == playStateMap[i].state) {
tptr = playStateMap[i].name;
break;
}
}
return tptr;
}
/* executed in some arbitrary thread */
void
mpvCallbackHandler (
void *cd
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
mpvData->hasEvent = 1;
}
void
mpvEventHandler (
ClientData cd
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
playstate stateflag;
if (mpvData->inst == NULL) {
return;
}
if (mpvData->hasEvent == 0) {
mpvData->timerToken = Tcl_CreateTimerHandler (CHKTIMER, &mpvEventHandler, mpvData);
return;
}
mpvData->hasEvent = 0;
mpv_event *event = mpv_wait_event (mpvData->inst, 0.0);
stateflag = stateMap[(int) mpvData->stateMapIdx[event->event_id]].stateflag;
while (event->event_id != MPV_EVENT_NONE) {#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "mpv: ev: %d %s\n", event->event_id, mpvStateToStr (event->event_id));
}
#endif
if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) {
mpv_event_property *prop = (mpv_event_property *) event->data;
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "mpv: ev: prop: %s\n", prop->name);
}
#endif
if (strcmp (prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
mpvData->tm = * (double *) prop->data;
}
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "mpv: ev: tm: %.2f\n", mpvData->tm);
}
#endif
} else if (strcmp (prop->name, "duration") == 0) { if (mpvData->state == PS_BUFFERING) {
mpvData->state = PS_PLAYING;
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "mpv: state: %s\n", stateToStr(mpvData->state));
}
#endif
}
if (prop->format == MPV_FORMAT_DOUBLE) {
mpvData->duration = * (double *) prop->data;
}
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "mpv: ev: dur: %.2f\n", mpvData->duration);
}
#endif
}
} else if (stateflag != PS_NONE) {
mpvData->state = stateflag;
#if MPVDEBUG } else {
if (mpvData->debugfh != NULL) { fprintf (mpvData->debugfh, "mpv: statev: %ds\n", stateToStr(mpvenData->evenst_idate));
}
#endif
}
mpv_event *event = mpv_wait_event (mpvData->inst, 0.0);
stateflag = stateMap[(int) mpvData->stateMapIdx[event->event_id]].stateflag;
}
mpvData->timerToken = Tcl_CreateTimerHandler (CHKTIMER, &mpvEventHandler, mpvData);
}
int
mpvDurationCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
double tm;
double dtm;
mpvData_t *mpvData = (mpvData_t *) cd;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
tm = mpvData->duration;
Tcl_SetObjResult (interp, Tcl_NewDoubleObj (tm));
}
return rc;
}
int
mpvGetTimeCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
double tm;
mpvData_t *mpvData = (mpvData_t *) cd;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
tm = mpvData->tm;
Tcl_SetObjResult (interp, Tcl_NewDoubleObj (tm));
}
return rc;
}
int
mpvIsPlayCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int rval;
mpvData_t *mpvData = (mpvData_t *) cd;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
rc = TCL_OK;
rval = 0;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
/*
* In order to match the implementation of VLC's internal
* isplaying command, return true if the player is paused * If the telnet VLC interface is ever dropped, this interface
* could be enhanced.
*/
if (mpvData->state == PS_OPENING ||
mpvData->state == PS_PLAYING ||
mpvData->state == PS_PAUSED) {
rval = 1;
}
Tcl_SetObjResult (interp, Tcl_NewIntObj (rval));
}
return rc;
}
int
mpvMediaCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
mpvData_t *mpvData = (mpvData_t *) cd;
char *fn;
struct stat statinfo;
double dval;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "media");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
if (mpvData->device != NULL) {
status = mpv_set_property (mpvData->inst, "audio-device", MPV_FORMAT_STRING, (void *) mpvData->device);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "set-ad:status:%d %s\n", status, mpv_error_string(status));
}
#endif
}
fn = Tcl_GetString(objv[1]);
if (stat (fn, &statinfo) != 0) {
rc = TCL_ERROR;
return rc;
}
/* reset the duration and time */
mpvData->duration = 0.0;
mpvData->tm = 0.0;
/* like many players, mpv will start playing when the 'loadfile'
* command is executed.
*/
const char *cmd[] = {"loadfile", fn, "replace", NULL};
status = mpv_command (mpvData->inst, cmd);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "loadfile:status:%d %s\n", status, mpv_error_string(status));
}
#endif
dval = 1.0;
status = mpv_set_property (mpvData->inst, "speed", MPV_FORMAT_DOUBLE, &dval);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "speed-1:status:%d %s\n", status, mpv_error_string(status));
}
#endif
}
return rc;
}
int
mpvPauseCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
mpvData_t *mpvData = (mpvData_t *) cd;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
if (mpvData->state != PS_PLAYING && mpvData->state != PS_PAUSED) {
;
} else if (mpvData->state == PS_PLAYING &&
mpvData->paused == 0) {
int val = 1;
status = mpv_set_property (mpvData->inst, "pause", MPV_FORMAT_FLAG, &val);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "pause-%d:status:%d %s\n", val, status, mpv_error_string(status));
}
#endif
mpvData->paused = 1;
mpvData->state = PS_PAUSED;
} else if (mpvData->state == PS_PAUSED &&
mpvData->paused == 1) {
int val = 0;
status = mpv_set_property (mpvData->inst, "pause", MPV_FORMAT_FLAG, &val);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "pause-%d:status:%d %s\n", val, status, mpv_error_string(status));
}
#endif
mpvData->paused = 0;
mpvData->state = PS_PLAYING;
}
}
return rc;
}
int
mpvPlayCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
mpvData_t *mpvData = (mpvData_t *) cd;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, "");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
if (mpvData->state == PS_PAUSED &&
mpvData->paused == 1) {
int val = 0;
status = mpv_set_property (mpvData->inst, "pause", MPV_FORMAT_FLAG, &val);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "play:status:%d %s\n", status, mpv_error_string(status));
}
#endif
mpvData->paused = 0;
mpvData->state = PS_PLAYING;
}
}
return rc;
}
int
mpvRateCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
mpvData_t *mpvData = (mpvData_t *) cd;
double rate;
double d;
if (objc != 1 && objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?rate?");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
if (objc == 2 && mpvData->state == PS_PLAYING) {
rc = Tcl_GetDoubleFromObj (interp, objv[1], &d);
if (rc == TCL_OK) {
rate = d;
status = mpv_set_property (mpvData->inst, "speed", MPV_FORMAT_DOUBLE, &rate);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "speed-%.2f:status:%d %s\n", rate, status, mpv_error_string(status));
}
#endif
}
}
status = mpv_get_property (mpvData->inst, "speed", MPV_FORMAT_DOUBLE, &rate);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "speed-get:status:%d %s\n", status, mpv_error_string(status));
}
#endif
Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) rate));
}
return rc;
}
int
mpvSeekCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
double tm;
double dtm;
mpvData_t *mpvData = (mpvData_t *) cd;
double pos;
double d;
char spos [40];
if (objc != 1 && objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?position?");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
if (objc == 2 && mpvData->state == PS_PLAYING) {
rc = Tcl_GetDoubleFromObj (interp, objv[1], &d);
if (rc == TCL_OK) {
pos = (double) d;
sprintf (spos, "%.1f", pos);
const char *cmd[] = { "seek", spos, "absolute", NULL };
status = mpv_command (mpvData->inst, cmd);
#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "seek-%s:status:%d %s\n", spos, status, mpv_error_string(status));
}
#endif
}
}
tm = mpvData->tm;
Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) tm));
}
return rc;
}
int
mpvStateCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
mpv_event_id plstate;
mpvData_t *mpvData = (mpvData_t *) cd;
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
plstate = mpvData->state;
Tcl_SetObjResult (interp, Tcl_NewStringObj (stateToStr(plstate), -1));
}
return rc;
}
int
mpvStopCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
int status;
mpvData_t *mpvData = (mpvData_t *) cd;
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
/* stop: stops playback and clears playlist */
/* difference: vlc's stop command does not clear the playlist */
const char *cmd[] = {"stop", NULL};
status = mpv_command (mpvData->inst, cmd);#if MPVDEBUG
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "stop:status:%d %s\n", status, mpv_error_string(status));
}
#endif
}
return rc;
}
int
mpvHaveAudioDevListCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
int rc;
rc = 1;
Tcl_SetObjResult (interp, Tcl_NewBooleanObj (rc));
return TCL_OK;
}
int
mpvVersionCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
Tcl_SetObjResult (interp, Tcl_NewStringObj (mpvData->version, -1));
return TCL_OK;
}
void
mpvClose (
mpvData_t *mpvData
)
{
int i;
if (mpvData->inst != NULL) {
mpv_terminate_destroy (mpvData->inst);
mpvData->inst = NULL;
}
if (mpvData->argv != NULL) {
for (i = 0; i < mpvData->argc; ++i) {
ckfree (mpvData->argv[i]);
}
ckfree (mpvData->argv);
mpvData->argv = NULL;
}
if (mpvData->device != NULL) {
free ((void *) mpvData->device);
mpvData->device = NULL;
}
mpvData->state = PS_STOPPED;
}
void
mpvExitHandler (
void *cd
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
Tcl_DeleteTimerHandler (mpvData->timerToken);
mpvClose (mpvData);
if (mpvData->debugfh != NULL) {
fclose (mpvData->debugfh);
mpvData->debugfh = NULL;
}
ckfree (cd);
}
int
mpvReleaseCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
Tcl_DeleteTimerHandler (mpvData->timerToken);
mpvClose (mpvData);
if (mpvData->debugfh != NULL) {
fclose (mpvData->debugfh);
mpvData->debugfh = NULL;
}
return TCL_OK;
}
int
mpvInitCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
char *tptr;
char *nptr;
int rc;
int i;
int len;
int status;
int gstatus;
mpvData_t *mpvData = (mpvData_t *) cd;
mpvData->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);
mpvData->argv[i] = nptr;
}
mpvData->argc = objc;
mpvData->argv[objc] = NULL;
rc = TCL_ERROR;
gstatus = 0;
if (mpvData->inst == NULL) {
mpvData->inst = mpv_create ();
if (mpvData->inst != NULL) {
status = mpv_initialize (mpvData->inst);
if (status < 0) { gstatus = status; }
double vol = 100.0;
mpv_set_property (mpvData->inst, "volume", MPV_FORMAT_DOUBLE, &vol);
mpv_observe_property(mpvData->inst, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpvData->inst, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_set_wakeup_callback (mpvData->inst, &mpvCallbackHandler, mpvData);
}
}
if (mpvData->inst != NULL && gstatus == 0) {
rc = TCL_OK;
}
return rc;
}
int
mpvAudioDevSetCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
int rc;
char *dev;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "deviceid");
return TCL_ERROR;
}
rc = TCL_OK;
if (mpvData->inst == NULL) {
rc = TCL_ERROR;
} else {
dev = Tcl_GetString(objv[1]);
if (mpvData->device != NULL) {
free ((void *) mpvData->device);
}
mpvData->device = NULL;
if (strlen (dev) > 0) {
mpvData->device = strdup (dev);
}
}
return rc;
}
int
mpvAudioDevListCmd (
ClientData cd,
Tcl_Interp* interp,
int objc,
Tcl_Obj * const objv[]
)
{
mpvData_t *mpvData = (mpvData_t *) cd;
Tcl_Obj *lobj;
Tcl_Obj *sobj;
mpv_node anodes;
mpv_node_list *infolist;
mpv_node_list *nodelist;
char *nmptr;
char *descptr;
int status;
if (mpvData->inst == NULL) {
return TCL_ERROR;
}
lobj = Tcl_NewListObj (0, NULL);
status = mpv_get_property (mpvData->inst, "audio-device-list", MPV_FORMAT_NODE, &anodes);
if (anodes.format != MPV_FORMAT_NODE_ARRAY) {
return TCL_ERROR;
}
nodelist = anodes.u.list;
for (int i = 0; i < nodelist->num; ++i) {
infolist = nodelist->values[i].u.list;
for (int j = 0; j < infolist->num; ++j) {
if (strcmp (infolist->keys[j], "name") == 0) {
nmptr = infolist->values[j].u.string;
} else if (strcmp (infolist->keys[j], "description") == 0) {
descptr = infolist->values[j].u.string;
} else {
if (mpvData->debugfh != NULL) {
fprintf (mpvData->debugfh, "dev: %s\n", infolist->keys[j]);
}
}
}
sobj = Tcl_NewStringObj (nmptr, -1);
Tcl_ListObjAppendElement (interp, lobj, sobj);
sobj = Tcl_NewStringObj (descptr, -1);
Tcl_ListObjAppendElement (interp, lobj, sobj);
}
mpv_free_node_contents (&anodes);
Tcl_SetObjResult (interp, lobj);
return TCL_OK;
}
static const EnsembleData mpvCmdMap[] = {
{ "audiodevlist", mpvAudioDevListCmd },
{ "audiodevset", mpvAudioDevSetCmd },
{ "close", mpvReleaseCmd },
{ "duration", mpvDurationCmd },
{ "gettime", mpvGetTimeCmd },
{ "init", mpvInitCmd },
{ "haveaudiodevlist", mpvHaveAudioDevListCmd },
{ "isplay", mpvIsPlayCmd },
{ "media", mpvMediaCmd },
{ "pause", mpvPauseCmd },
{ "play", mpvPlayCmd },
{ "rate", mpvRateCmd },
{ "seek", mpvSeekCmd },
{ "state", mpvStateCmd },
{ "stop", mpvStopCmd },
{ "version", mpvVersionCmd },
{ NULL, NULL }
};
int
Tclmpv_Init (Tcl_Interp *interp)
{
Tcl_Namespace *nsPtr = NULL;
Tcl_Command ensemble = NULL;
Tcl_Obj *dictObj = NULL;
Tcl_DString ds;
mpvData_t *mpvData;
unsigned int ivers;
int i;
int rc;
int debug;
const char *nsName = "::tcl::tclmpv";
const char *cmdName = nsName + 5;
char tvers [20];
if (!Tcl_InitStubs (interp,"8.3",0)) {
return TCL_ERROR;
}
debug = 0;
#if MPVDEBUG
debug = 1;
#endif
mpvData = (mpvData_t *) ckalloc (sizeof (mpvData_t));
mpvData->interp = interp;
mpvData->inst = NULL;
mpvData->argv = NULL;
mpvData->state = PS_NONE;
mpvData->device = NULL;
mpvData->paused = 0;
mpvData->duration = 0.0;
mpvData->tm = 0.0;
mpvData->hasEvent = 0;
mpvData->timerToken = Tcl_CreateTimerHandler (CHKTIMER, &mpvEventHandler, mpvData);
mpvData->debugfh = NULL;
for (i = 0; i < stateMapIdxMax; ++i) {
mpvData->stateMapIdx[i] = 0;
}
for (i = 0; i < stateMapMax; ++i) {
mpvData->stateMapIdx[stateMap[i].state] = i;
}
if (debug) {
mpvData->debugfh = fopen ("mpvdebug.txt", "w+");
}
nsPtr = Tcl_FindNamespace(interp, nsName, NULL, 0);
if (nsPtr == 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; mpvCmdMap[i].name != NULL; ++i) {
Tcl_Obj *nameObj;
Tcl_Obj *fqdnObj;
nameObj = Tcl_NewStringObj (mpvCmdMap[i].name, -1);
fqdnObj = Tcl_NewStringObj (Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
Tcl_AppendStringsToObj (fqdnObj, "::", mpvCmdMap[i].name, NULL);
Tcl_DictObjPut (NULL, dictObj, nameObj, fqdnObj);
if (mpvCmdMap[i].proc) {
Tcl_CreateObjCommand (interp, Tcl_GetString (fqdnObj),
mpvCmdMap[i].proc, (ClientData) mpvData, NULL);
}
}
if (ensemble) {
Tcl_SetEnsembleMappingDict (interp, ensemble, dictObj);
}
Tcl_DStringFree(&ds);
ivers = mpv_client_api_version();
sprintf (tvers, "%d.%d", ivers >> 16, ivers & 0xFF);
strcpy (mpvData->version, tvers);
/* If the 'package ifneeded' and package provides do
* not match, tcl fails. Can't really use the mpv
* version number here.
*/
Tcl_PkgProvide (interp, cmdName+2, "1.1");
return TCL_OK;
}
======
<<categories>>Package | Multimedia