/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxplayer.cpp,v 1.21.2.9 2004/10/18 18:55:17 rggammon Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxtypes.h"

#include "hxplayer.h"
#include "hxgerror.h"
#include "hlxclib/string.h"

#include "hxwintyp.h"

#ifdef G_OS_UNIX
#include <X11/extensions/XShm.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#endif

#include <gtk/gtkmain.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

/* hxclientkit includes */
#include "HXClientTypes.h"
#include "HXClientCFuncs.h"
#include "HXClientCallbacks.h"
#include "HXClientConstants.h"
#include "HXClientCOMAccess.h"

#include "hx-i18n.h"

extern "C"
{
#include "hxmarshal.h"
}

#define SLEEP_TIME 10
#define INFINITE_DURATION 1981342000 /* 1981342000 is 22d+22h+22m+22s */

enum {
    PLAY_SIGNAL,
    STOP_SIGNAL,
    PAUSE_SIGNAL,
    CONTACTING_SIGNAL,
    BUFFERING_SIGNAL,
    LENGTH_CHANGED_SIGNAL,
    SEEK_SIGNAL,
    ERROR_SIGNAL,
    HXERROR_SIGNAL,
    IDEAL_SIZE_CHANGED_SIGNAL,
    VOLUME_CHANGED_SIGNAL,
    MUTE_CHANGED_SIGNAL,
    GOTO_URL_SIGNAL,
    OPEN_WINDOW_SIGNAL,
    CLIP_BANDWIDTH_CHANGED_SIGNAL,
    REQUEST_UPGRADE_SIGNAL,    
    GROUPS_CHANGED_SIGNAL,
    GROUP_STARTED_SIGNAL,
    TITLE_CHANGED_SIGNAL,
    REQUEST_AUTHENTICATION_SIGNAL,
    STATUS_CHANGED_SIGNAL,
    CONTENT_CONCLUDED_SIGNAL,
    CONTENT_STATE_CHANGED_SIGNAL,
    HAS_FEATURE_SIGNAL,
    START_SEEKING_SIGNAL,
    STOP_SEEKING_SIGNAL,
    VISUAL_STATE_CHANGED_SIGNAL,
    
    LAST_SIGNAL
};

enum {
    PROP_0,
    PROP_LOOP,
    PROP_LOOP_COUNT,
    PROP_SHUFFLE,
};

static guint signals[LAST_SIGNAL] = { 0 };

    /* info for the timeout callback */
static guint g_pump_timer_id = 0;
static guint g_engine_ref_count = 0;
static GList* g_players_list = NULL;
static Display* g_dpy = NULL;

/* extension info */
static gboolean g_xshm_present = FALSE;
static gint     g_xshm_event_base = -1;

/* GObject, GtkObject methods */
static void     hx_player_class_init            (HXPlayerClass*  klass);
static void     hx_player_init                  (HXPlayer*       hxplayer,
                                                 HXPlayerClass*  klass);
static void     hx_player_finalize              (GObject*        object);
static void     hx_player_unrealize             (GtkWidget*      widget);
static void     hx_player_realize               (GtkWidget*      widget);
static gboolean hx_player_expose                (GtkWidget*      widget,
                                                 GdkEventExpose* event);
static void     hx_player_size_allocate         (GtkWidget*      widget,
                                                 GtkAllocation*  allocation);
static gboolean hx_player_motion_notify_event   (GtkWidget*      widget,
                                                 GdkEventMotion* event);
static void     hx_player_set_property          (GObject*        object,
                                                 guint           prop_id,
                                                 const GValue*   value,
                                                 GParamSpec*     pspec);
static void     hx_player_get_property          (GObject*        object,
                                                 guint           prop_id,
                                                 GValue*         value,
                                                 GParamSpec*     pspec);
static void     hx_player_shuffle               (HXPlayer*       player);

#ifdef G_OS_UNIX
// We use an event filter here rather than overriding the events
// in the widget class. This gives us access to raw XEvents, suitable
// for passing into the core.
static GdkFilterReturn
                hx_player_event_filter (GdkXEvent* gxevent,
                                        GdkEvent*  gevent,
                                        gpointer   player);
static gboolean hx_player_pump         (gpointer   user_data);
#endif


void
OnContentStateChanged(void* userInfo,
                      int   oldContentState,
                      int   newContentState)
{
    HXPlayer* player;
    HXContentStateType new_state, old_state;
    
    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    new_state = (HXContentStateType) newContentState;
    old_state = (HXContentStateType) oldContentState;

    if(G_OBJECT(player)->ref_count == 0)
    {
        /* XXXRGG: hx_player_finalize calls stop, which in turn
           calls this function. Emitting signals while being
           finalized doesn't seem to work with the version of glib
           I'm using now, so handle this case here. */
        return;
    }
    
    int signal = -1;
    switch (newContentState)
    {
        case kContentStatePlaying:
            signal = signals[PLAY_SIGNAL];
            break;

        case kContentStateStopped:
            signal = signals[STOP_SIGNAL];
            break;

        case kContentStatePaused:
            signal = signals[PAUSE_SIGNAL];
            break;
            
        case kContentStateLoading:
        case kContentStateContacting:
        case kContentStateNotLoaded:
            break;

        default:
            // unknown/unhandled event
            g_assert_not_reached();
    }

    if(signal >= 0)
    {
        g_signal_emit(G_OBJECT(player), signal, 0);
    }
    
    g_signal_emit(G_OBJECT(player), signals[CONTENT_STATE_CHANGED_SIGNAL], 0,
                  old_state, new_state);
}


void
OnVisualStateChanged(void* userInfo,
                     bool  hasVisualContent)
{
    HXPlayer* player;
    GtkWidget* widget;    

    widget = GTK_WIDGET(userInfo);
    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);
    
    g_signal_emit(G_OBJECT(player), signals[VISUAL_STATE_CHANGED_SIGNAL], 0,
                  (gboolean) hasVisualContent);
    
    gdk_window_invalidate_rect(widget->window, &widget->allocation, FALSE);
}


void
OnIdealSizeChanged(void*  userInfo,
                   SInt32 idealWidth,
                   SInt32 idealHeight)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[IDEAL_SIZE_CHANGED_SIGNAL], 0,
                  idealWidth, idealHeight);
}


void
OnLengthChanged(void*  userInfo,
                UInt32 length)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit (G_OBJECT (player), signals[LENGTH_CHANGED_SIGNAL], 0, (guint)length);
}


void
OnTitleChanged(void*       userInfo,
               const char* pTitle)
{
    HXPlayer* player;
    gsize in;
    gsize out;
    gchar* title = NULL;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    if(pTitle)
    {
        in = strlen(pTitle) + 1;
        out = 0;
        title = g_convert(pTitle, in - 1, "UTF-8", "ISO-8859-1", &in, &out, NULL);
    }
    
    g_signal_emit (G_OBJECT (player), signals[TITLE_CHANGED_SIGNAL], 0, title);

    g_free(title);
}


void
OnGroupsChanged(void* userInfo)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[GROUPS_CHANGED_SIGNAL], 0);
}


void
OnGroupStarted(void*  userInfo,
               UInt16 groupIndex)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);    

    g_signal_emit(G_OBJECT(player), signals[GROUP_STARTED_SIGNAL], 0, (guint)groupIndex);
}

void
OnContacting( void*       userInfo,
              const char* pContactingText)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[CONTACTING_SIGNAL], 0, pContactingText);    
}

void
OnBuffering(void*  userInfo,
            UInt32 bufferingReason,
            UInt16 bufferPercent)
{
    HXPlayer* player;    
    
    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[BUFFERING_SIGNAL], 0, bufferingReason, (guint)bufferPercent);
}

void
OnContentConcluded(void* userInfo)
{
    HXPlayer* player;    
    guint cur_group;
    guint group_count;
    
    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[CONTENT_CONCLUDED_SIGNAL], 0);    

    if(player->shuffle)
    {
        hx_player_shuffle(player);
    }
    else
    {
        if(player->loop || player->loop_count > 0)
        {
            cur_group = hx_player_get_current_group(player);
            group_count = hx_player_get_group_count(player);
        
            if(cur_group == group_count)
            {
                if(!player->loop_count ||
                   player->cur_loop < player->loop_count)
                {
                    hx_player_set_current_group(player, 0);
                    hx_player_play(player);
                    player->cur_loop++;
                }
                else
                {
                    player->cur_loop = 0;    
                }
            }
        }
    }
}


void
OnStatusChanged(void*       userInfo,
                const char* pStatus)
{    
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[STATUS_CHANGED_SIGNAL], 0, pStatus);
}

void
OnVolumeChanged(void*  userInfo,
                UInt16 volume)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[VOLUME_CHANGED_SIGNAL], 0, (guint)volume);
}


void
OnMuteChanged(void* userInfo,
              bool  hasMuted)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    g_signal_emit(G_OBJECT(player), signals[MUTE_CHANGED_SIGNAL], 0, (gboolean)hasMuted);
}


void
OnClipBandwidthChanged(void*  userInfo,
                       SInt32 clipBandwidth)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);
    
    g_signal_emit(G_OBJECT(player), signals[CLIP_BANDWIDTH_CHANGED_SIGNAL], 0, (gint)clipBandwidth);
}

void
OnErrorOccurred(void*       userInfo,
                UInt32      hxCode,
                UInt32      userCode,
                const char* pErrorString,
                const char* pUserString,
                const char* pMoreInfoURL)
{
    HXPlayer* player;
    GError* error;
    guint hxerror_id;
    guint error_id;
    
    player = HX_PLAYER(userInfo);
    g_return_if_fail(player != NULL);

    error = hx_error_new(hxCode, userCode, pErrorString, pUserString, pMoreInfoURL);

    error_id = g_signal_handler_find(G_OBJECT(player),
                                     G_SIGNAL_MATCH_ID,
                                     signals[ERROR_SIGNAL],
                                     0,    // detail 
                                     NULL, // closure
                                     NULL, // func
                                     NULL  // data
                                     );

    hxerror_id = g_signal_handler_find(G_OBJECT(player),
                                       G_SIGNAL_MATCH_ID,
                                       signals[HXERROR_SIGNAL],
                                       0,    // detail 
                                       NULL, // closure
                                       NULL, // func
                                       NULL  // data
                                       );

    if(error_id == 0 && hxerror_id == 0)
    {
        /* No error signal handlers found -- use g_warning to display instead.
           This is particularly useful for handling errors on startup
           (tbd: might be a better idea to have hx_player_new return a GError) */
        g_warning(_("HXPlayer: Error 0x%08x: \"%s\""), (guint)hxCode, error->message);
    }
    
    /* We allocate a HXError and free it. That's not the way functions
       work, but it's a grey area for signals.*/
    g_signal_emit(G_OBJECT(player), signals[ERROR_SIGNAL], 0, error);

    /* Also emit a signal with all the helix error information in it.
       This is used in the embedded player */
    g_signal_emit(G_OBJECT(player), signals[HXERROR_SIGNAL], 0,
                  (guint)hxCode, (guint)userCode,
                  pErrorString, pUserString, pMoreInfoURL);

    g_free(error);
}

bool
GoToURL(void*       userInfo,
        const char* pURL,
        const char* pTarget,
        bool isPlayerURL)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_val_if_fail(player != NULL, false);

    if(isPlayerURL)
    {
        g_signal_emit(G_OBJECT(player), signals[OPEN_WINDOW_SIGNAL], 0, pURL, pTarget);
    }
    else
    {
        g_signal_emit(G_OBJECT(player), signals[GOTO_URL_SIGNAL], 0, pURL, pTarget);
    }

    return true;
}

bool
RequestAuthentication(void*       userInfo,
                      const char* pServer,
                      const char* pRealm,
                      bool        isProxyServer)
{
    HXPlayer* player;

    player = HX_PLAYER(userInfo);
    g_return_val_if_fail(player != NULL, false);

    g_signal_emit(G_OBJECT(player), signals[REQUEST_AUTHENTICATION_SIGNAL], 0,
                  pServer, pRealm, isProxyServer);

    return false;
}

bool
RequestUpgrade(void*       userInfo,
               const char* url,
               UInt32      numOfComponents,
               const char* componentNames[],
               bool        isBlocking)
{
    HXPlayer* player;
    GList* components_list = NULL;
    GList* components_list_iter;
    guint i;
    gboolean duplicate;
    
    player = HX_PLAYER(userInfo);
    g_return_val_if_fail(player != NULL, false);

    /* See comment in OnErrorOccurred */
    ClientPlayerStop(player->player);
    g_signal_emit(G_OBJECT(player), signals[STOP_SIGNAL], 0);
    
    for(i = 0; i < numOfComponents; i++)
    {
        duplicate = FALSE;
        
        /* Check for duplicate components */
        components_list_iter = components_list;
        while(components_list_iter)
        {
            duplicate = strcmp(componentNames[i], (char*)components_list_iter->data) == 0;
            components_list_iter = g_list_next(components_list_iter);
            if(duplicate)
            {
                break;
            }
        }
        
        if(!duplicate)
        {
            /* Add it to the list if there were no duplicates*/
            components_list = g_list_append(components_list, (void*)componentNames[i]);
        }

    }
                                    
    // XXXRGG: This seems to get called for file:// urls where the file
    // doesn't exist. FIXME
    g_signal_emit(G_OBJECT(player), signals[REQUEST_UPGRADE_SIGNAL], 0,
                  url, components_list, (gboolean)isBlocking);
    
    g_list_free(components_list);
                                    
    return false;
}

bool
HasComponent(void*       userInfo,
             const char* componentName)
{
    (void)userInfo;
    (void)componentName;
    
    g_warning("Unhandled request for component %s",
              componentName);
    
    return false;
}

static const HXClientCallbacks g_hxcallbacks =
{
    OnVisualStateChanged,
    OnIdealSizeChanged,
    OnLengthChanged,
    OnTitleChanged,
    OnGroupsChanged,
    OnGroupStarted,
    OnContacting,
    OnBuffering,
    OnContentStateChanged,
    OnContentConcluded,
    OnStatusChanged,
    OnVolumeChanged,
    OnMuteChanged,
    OnClipBandwidthChanged,
    OnErrorOccurred,
    GoToURL,
    RequestAuthentication,
    RequestUpgrade,
    HasComponent,

    NULL, // PrivateCallback1
    NULL, // PrivateCallback2
};

bool
HasFeature( const char* szFeatureName )
{
    gboolean result = FALSE;
    GList* iter;

    iter = g_players_list;
    while(iter && !result)
    {        
        g_signal_emit(G_OBJECT(iter->data),
                      signals[HAS_FEATURE_SIGNAL], 0,
                      (gchar*)szFeatureName, &result);

        iter = g_list_next(iter);
    }
        
    return (result)? true: false;
}

/* These functions are actually defined in hxprefs.cpp */
bool ReadPreference  (const char*          pPrekKey,
                      unsigned char*       pValueBuffer,
                      UInt32               bufferLength,
                      UInt32*              pUsedBufferLength);
bool WritePreference (const char*          pPrekKey,
                      const unsigned char* pValueBuffer,
                      UInt32               bufferLength);
bool DeletePreference(const char*          pPrekKey);


static const HXClientEngineCallbacks g_hxclientengine_callbacks =
{
    ReadPreference,
    WritePreference,
    DeletePreference,
    HasFeature
};

GType
hx_player_get_type(void)
{
    static GType hxplayer_type = 0;

    if (!hxplayer_type)
    {
        static const GTypeInfo hxplayer_info =
        {
            sizeof (HXPlayerClass),
            NULL,           /* base_init */
            NULL,           /* base_finalize */
            (GClassInitFunc) hx_player_class_init,
            NULL,           /* class_finalize */
            NULL,           /* class_data */
            sizeof (HXPlayer),
            0,              /* n_preallocs */
            (GInstanceInitFunc) hx_player_init,
            NULL,           /* value_table */
        };
                                                                                    
        hxplayer_type = g_type_register_static (GTK_TYPE_WIDGET, "HXPlayer",
                                                &hxplayer_info, (GTypeFlags)0);
    }
                                                                                
    return hxplayer_type;   
}

static void
hx_player_class_init(HXPlayerClass* klass)
{
    GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
    GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);

#ifdef ENABLE_NLS
    /* gettext support */
    bindtextdomain (PACKAGE, LOCALEDIR);

#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
    bind_textdomain_codeset(PACKAGE, "UTF-8");
#endif
#endif

    gobject_class->finalize = hx_player_finalize;
    gobject_class->set_property = hx_player_set_property;
    gobject_class->get_property = hx_player_get_property;

    widget_class->realize = hx_player_realize;
    widget_class->unrealize = hx_player_unrealize;
    widget_class->size_allocate = hx_player_size_allocate; 
    widget_class->expose_event = hx_player_expose; 
    widget_class->motion_notify_event = hx_player_motion_notify_event;
            
    /* Action signals */    
    signals[PLAY_SIGNAL] =
        g_signal_new("play",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, play),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE, 0);  

    signals[STOP_SIGNAL] =
        g_signal_new("stop",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, stop),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE, 0);  

    signals[PAUSE_SIGNAL] =
        g_signal_new("pause",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, pause),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE, 0);  

    signals[CONTACTING_SIGNAL] =
        g_signal_new("contacting",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, contacting),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__STRING,
                      G_TYPE_NONE, 1, G_TYPE_STRING);

    signals[BUFFERING_SIGNAL] =
        g_signal_new("buffering",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, buffering),
                      NULL, NULL,
                      g_cclosure_user_marshal_VOID__UINT_UINT,
                      G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);  


    signals[LENGTH_CHANGED_SIGNAL] =
        g_signal_new("length_changed",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, length_changed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__UINT,
                      G_TYPE_NONE, 1, G_TYPE_UINT);  

    signals[SEEK_SIGNAL] =
        g_signal_new("seek",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, seek),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__UINT,
                      G_TYPE_NONE, 1, G_TYPE_UINT);  

    signals[START_SEEKING_SIGNAL] =
        g_signal_new("start_seeking",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, start_seeking),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE, 0);  

    signals[STOP_SEEKING_SIGNAL] =
        g_signal_new("stop_seeking",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, stop_seeking),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE, 0);  
    
    signals[ERROR_SIGNAL] =
        g_signal_new("error",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, error),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__POINTER,
                      G_TYPE_NONE, 1, G_TYPE_POINTER);  

    signals[HXERROR_SIGNAL] =
        g_signal_new("hxerror",
                      G_OBJECT_CLASS_TYPE(gobject_class),
                      (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                      G_STRUCT_OFFSET(HXPlayerClass, error),
                      NULL, NULL,
                      g_cclosure_user_marshal_VOID__UINT_UINT_STRING_STRING_STRING,
                      G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);  

    signals[IDEAL_SIZE_CHANGED_SIGNAL] =
        g_signal_new("ideal_size_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, ideal_size_changed),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__INT_INT,
                     G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);  

    signals[VOLUME_CHANGED_SIGNAL] =
        g_signal_new("volume_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, volume_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__UINT,
                     G_TYPE_NONE, 1, G_TYPE_UINT);  

    signals[MUTE_CHANGED_SIGNAL] =
        g_signal_new("mute_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, mute_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__BOOLEAN,
                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);  

    signals[GOTO_URL_SIGNAL] =
        g_signal_new("goto_url",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, goto_url),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__STRING_STRING,
                     G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);  

    signals[OPEN_WINDOW_SIGNAL] =
        g_signal_new("open_window",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, open_window),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__STRING_STRING,
                     G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);  

    signals[CLIP_BANDWIDTH_CHANGED_SIGNAL] =
        g_signal_new("clip_bandwidth_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, clip_bandwidth_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__INT,
                     G_TYPE_NONE, 1, G_TYPE_INT);  

    signals[REQUEST_UPGRADE_SIGNAL] =
        g_signal_new("request_upgrade",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, request_upgrade),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__STRING_POINTER_BOOLEAN,
                     G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);

    signals[GROUPS_CHANGED_SIGNAL] =
        g_signal_new("groups_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, groups_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE, 0);

    signals[GROUP_STARTED_SIGNAL] =
        g_signal_new("group_started",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, group_started),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__UINT,
                     G_TYPE_NONE, 1, G_TYPE_UINT);

    signals[TITLE_CHANGED_SIGNAL] =
        g_signal_new("title_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, title_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__STRING,
                     G_TYPE_NONE, 1, G_TYPE_STRING);

    signals[REQUEST_AUTHENTICATION_SIGNAL] =
        g_signal_new("request_authentication",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, request_authentication),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__STRING_STRING_BOOLEAN,
                     G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);

    signals[STATUS_CHANGED_SIGNAL] =
        g_signal_new("status_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, status_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__STRING,
                     G_TYPE_NONE, 1, G_TYPE_STRING);

    signals[CONTENT_CONCLUDED_SIGNAL] =
        g_signal_new("content_concluded",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, content_concluded),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__VOID,
                     G_TYPE_NONE, 0);

    signals[CONTENT_STATE_CHANGED_SIGNAL] =
        g_signal_new("content_state_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, content_concluded),
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__UINT_UINT,
                     G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);


    signals[HAS_FEATURE_SIGNAL] =
        g_signal_new("has_feature",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, has_feature),
                     NULL, NULL,
                     g_cclosure_user_marshal_BOOLEAN__STRING,
                     G_TYPE_BOOLEAN, 1, G_TYPE_STRING);

    signals[VISUAL_STATE_CHANGED_SIGNAL] =
        g_signal_new("visual_state_changed",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
                     G_STRUCT_OFFSET(HXPlayerClass, visual_state_changed),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__BOOLEAN,
                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);    
        
    /* Properties */
    g_object_class_install_property (gobject_class,
                                     PROP_LOOP,
                                     g_param_spec_boolean ("loop",
                                                           _("Loop"),
                                                           _("Player should loop (indefinitely, if loop_count is not set)"),
                                                           FALSE,
                                                           (GParamFlags)G_PARAM_READWRITE));

    g_object_class_install_property (gobject_class,
                                     PROP_LOOP_COUNT,
                                     g_param_spec_uint ("loop_count",
                                                        _("Loop count"),
                                                        _("Number of times to repeat the opened content"),
                                                        0, UINT_MAX, 0,
                                                        (GParamFlags)G_PARAM_READWRITE));

    g_object_class_install_property (gobject_class,
                                     PROP_SHUFFLE,
                                     g_param_spec_boolean ("shuffle",
                                                          _("Shuffle"),
                                                          _("Shuffle opened content"),
                                                          FALSE,
                                                          (GParamFlags)G_PARAM_READWRITE));

    
    /* Initialize reference counting for the engine pump */
    g_pump_timer_id = 0;
    g_engine_ref_count = 0;

    /* Seed the random func (for use with shuffle) */
    srand(time(NULL));

    /* Override preferences callbacks */
    ClientEngineSetCallbacks(&g_hxclientengine_callbacks);
}


static void
hx_player_init(HXPlayer*      player,
               HXPlayerClass* /* klass */)
{
    GTK_WIDGET_SET_FLAGS (player, GTK_CAN_FOCUS);
    
    player->player = NULL;
    player->state = HX_CONTENT_STATE_NOT_LOADED;
    player->logo_pixmap = NULL;
    player->logo_height = 0;
    player->logo_width = 0;
    player->window = NULL;
    player->shuffle = FALSE;
    player->loop_count = 0;
    player->cur_loop = 0;
    player->loop = FALSE;
    player->group_title_buf = NULL;
    player->title_buf = NULL;
}


/**
 * hx_player_new:
 *
 * Creates a new #HXPlayer widget.
 *
 * @returns: a new #HXPlayer.
 *
 **/
GtkWidget*
hx_player_new(void)
{
    HXPlayer* player = (HXPlayer*)g_object_new(HX_TYPE_PLAYER, NULL);
    if(player)
    {
        g_players_list = g_list_append(g_players_list, player);
        
        /* Allocate a Window structure */
        player->window = g_new0(SHXClientWindow, 1);

        /* Add a reference to the engine "pump", start it if we're
           the first widget */
        HXPlayerClass* klass;
        klass = HX_PLAYER_GET_CLASS(player);

        if(g_engine_ref_count == 0)
        {
#ifdef G_OS_UNIX
            // Create a display for the player
            if(!g_dpy)
            {
                g_dpy = XOpenDisplay(NULL);
            }

            // Query for extensions for event handling purposes within
            // the pump callback.
            gint ignore;
            g_xshm_present = XQueryExtension(g_dpy,
                                             "MIT-SHM",
                                             &ignore,
                                             &g_xshm_event_base,
                                             &ignore);
            
            g_pump_timer_id = gtk_timeout_add(SLEEP_TIME,
                                              hx_player_pump,
                                              NULL);
#endif
        }
        
        g_engine_ref_count++;
    }

    /* Initialize the engine */
    if(!ClientPlayerCreate(&player->player,
                           player->window,
                           player,
                           &g_hxcallbacks))
    {
        /*  XXXRGG: Move this to the top level client once we get
            good error signal handling working. */
        
        gchar* helix_libs = getenv("HELIX_LIBS");
        if(helix_libs)
        {
            g_error(_("Could not create helix engine. Make sure your helix "
                      "libs are installed at: HELIX_LIBS=%s"), helix_libs);
        }
        else
        {            
            g_error(_("Could not create helix engine. You must run:\n"
                    "export HELIX_LIBS=<path to your helix libs>"));
        }
    }
    
    return GTK_WIDGET(player);
}


/**
 * hx_player_open_url:
 * @player: a #HXPlayer
 * @url: the url to open
 *
 * Opens a url
 *
 * @returns: whether the url opened successfully.
 *
 **/
gboolean
hx_player_open_url(HXPlayer*    player,
                   const gchar* url)
{
    bool bResult;
    
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(url != NULL, FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    bResult = ClientPlayerOpenURL(player->player, url, NULL);

    if(player->shuffle)
    {
        hx_player_shuffle(player);
    }

    return (bResult)? TRUE: FALSE;
}

/**
 * hx_player_get_url:
 * @player: a #HXPlayer
 *
 * Returns the currently open url
 *
 * @returns: The url.
 *
 **/
G_CONST_RETURN gchar*
hx_player_get_url(HXPlayer *player)
{
    UInt32 buf_desired, buf_used;
    
    g_return_val_if_fail(player != NULL, NULL);

    g_free(player->last_opened_url_buf);
    player->last_opened_url_buf = NULL;
    
    /* Get the size */
    ClientPlayerGetOpenedURL(player->player, NULL, 0, &buf_desired);
    if(buf_desired == 0)
    {
        return NULL;
    }

    player->last_opened_url_buf = (gchar*)g_try_malloc(buf_desired + 1);
    g_return_val_if_fail(player->last_opened_url_buf, NULL);

    ClientPlayerGetOpenedURL(player->player,
                             player->last_opened_url_buf,
                             buf_desired,
                             &buf_used);

    g_assert(buf_used == buf_desired);

    return player->last_opened_url_buf;
}


/**
 * hx_player_open_url_with_mime_type:
 * @player: a #HXPlayer
 * @url: the url to open
 * @mime_type: the mime type
 *
 * Opens a url, specifying a mime type.
 * XXXRGG: When would I want to do this?
 *
 * @returns: whether the url opened successfully.
 *
 **/
gboolean
hx_player_open_url_with_mime_type(HXPlayer*    player,
                                  const gchar* url,
                                  const gchar* mime_type)
{
    bool bResult;
    
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(url != NULL, FALSE);
    g_return_val_if_fail(mime_type != NULL, FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    bResult = ClientPlayerOpenURL(player->player, url, mime_type);

    if(player->shuffle)
    {
        hx_player_shuffle(player);
    }

    return (bResult)? TRUE: FALSE;
}

/**
 * hx_player_open_file:
 * @player: a #HXPlayer
 * @filename: the full path to the file to open
 *
 * Opens a file. Equivalent to calling hx_player_open_file()
 * with a file:// url
 *
 * @returns: whether the file opened successfully.
 *
 **/
gboolean
hx_player_open_file(HXPlayer*    player,
                    const gchar* filename)
{
    gchar* url;
    gboolean result;
    
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(filename != NULL, FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    url = g_strdup_printf("file://%s", filename);
    
    result = hx_player_open_url(player, url);
    
    g_free(url);

    return result;
}

/**
 * hx_player_get_content_state:
 * @player: a #HXPlayer
 *
 * Opens a file. Equivalent to calling hx_player_open_file()
 * with a "file://" url
 *
 * @returns: #GtkContentStateType
 *
 **/
HXContentStateType
hx_player_get_content_state(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player),
                         HX_CONTENT_STATE_NOT_LOADED);
    
    g_return_val_if_fail(player->player != NULL,
                         HX_CONTENT_STATE_NOT_LOADED);

    return (HXContentStateType) ClientPlayerGetContentState(player->player);
}

/**
 * hx_player_play:
 * @player: a #HXPlayer
 *
 * Starts playback of the opened clip
 *
 **/
void
hx_player_play(HXPlayer* player)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);
    
    ClientPlayerPlay(player->player);
}

/**
 * hx_player_pause:
 * @player: a #HXPlayer
 *
 * Pauses playback of a clip
 *
 **/
void
hx_player_pause(HXPlayer* player)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerPause(player->player);
}

/**
 * hx_player_stop:
 * @player: a #HXPlayer
 *
 * Stops playback of a clip
 *
 **/
void
hx_player_stop(HXPlayer* player)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerStop(player->player);
}

/**
 * hx_player_start_seeking:
 * @player: a #HXPlayer
 *
 * Call this function before calling hx_player_set_position()
 *
 **/
void
hx_player_start_seeking(HXPlayer* player)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    g_signal_emit(G_OBJECT(player), signals[START_SEEKING_SIGNAL], 0);

    ClientPlayerStartSeeking(player->player);
}

/**
 * hx_player_set_position:
 * @player: a #HXPlayer
 * @position: position in file, meausred in seconds
 *
 **/
void
hx_player_set_position(HXPlayer* player,
                       guint     position)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    g_signal_emit(G_OBJECT(player), signals[SEEK_SIGNAL], 0,
                  position);

    ClientPlayerSetPosition(player->player, position);    
}

/**
 * hx_player_stop_seeking:
 * @player: a #HXPlayer
 *
 * Call when the seeking operation is complete
 *
 **/
void
hx_player_stop_seeking(HXPlayer* player)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerStopSeeking(player->player);

    g_signal_emit(G_OBJECT(player), signals[STOP_SEEKING_SIGNAL], 0);
}
    
/**
 * hx_player_get_position:
 * @player: a #HXPlayer
 *
 * Get the current position in the clip, measured in seconds
 *
 * @returns: the current position
 *
 **/
guint
hx_player_get_position(HXPlayer* player)
{
    guint pos;
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);
    
    pos = ClientPlayerGetPosition(player->player);
    
    return pos;
}

/**
 * hx_player_get_length:
 * @player: a #HXPlayer
 *
 * Get the total length of the clip, measured in seconds
 *
 * @returns: the total clip length
 *
 **/
guint
hx_player_get_length(HXPlayer* player)
{
    UInt32 nPos;
    
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    nPos = ClientPlayerGetLength(player->player);

    if(nPos == INFINITE_DURATION)
    {
        /* Check for infinite clip duration using
           hx_player_is_infinite_duration. If this function
           ever returns 22:22:22:22, it means the clip is
           actually that length. */
        nPos = 0;
    }
    
    return (guint) nPos;
}

/**
 * hx_player_is_live:
 * @player: a #HXPlayer
 *
 * @returns: whether the clip being played is live
 *
 **/
gboolean
hx_player_is_live(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    return ClientPlayerIsLive(player->player);
}

/**
 * hx_player_is_infinite_duration:
 * @player: a #HXPlayer
 *
 * @returns: whether the clip being played is of infinite duration
 *
 **/
gboolean
hx_player_is_indefinite_duration(HXPlayer* player)
{
    UInt32 nPos;
    
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    nPos = ClientPlayerGetLength(player->player);

    return (nPos == INFINITE_DURATION)? TRUE: FALSE;
}

/**
 * hx_player_has_visual_content:
 * @player: a #HXPlayer
 *
 * @returns: %TRUE if the content being played has visual content
 *
 **/
gboolean
hx_player_has_visual_content(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);

    if(!player->player)
    {
        /* No visual content yet -- player engine isn't initialized */
        return FALSE;
    }
    return ClientPlayerHasVisualContent(player->player);
}

/**
 * hx_player_get_ideal_size:
 * @player: a #HXPlayer
 * @width: ideal width
 * @height: ideal height
 *
 * Returns the ideal size of the visual content being displayed
 * (eg: the resolution at which it was encoded)
 *
 **/
void
hx_player_get_ideal_size(HXPlayer* player,
                         gint*     width,
                         gint*     height)
{
    SInt32 w = 0;
    SInt32 h = 0;

    *width = 0;
    *height = 0;
    
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(width != NULL && height != NULL);
    
    ClientPlayerGetIdealSize(player->player, &w, &h);
    
    *width = w;
    *height = h;
}

void
hx_player_get_logo_size(HXPlayer* player,
                        gint*     width,
                        gint*     height)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(width != NULL && height != NULL);
    
    *width = player->logo_width;
    *height = player->logo_height;
}

/**
 * hx_player_get_clip_bandwidth:
 * @player: a #HXPlayer
 *
 * The bandwidth required by the clip being streamed,
 * measured in bytes per second.
 *
 * @returns: the bandwidth
 *
 **/
guint
hx_player_get_clip_bandwidth(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetClipBandwidth(player->player);
}

/**
 * hx_player_get_source_count:
 * @player: a #HXPlayer
 *
 * The number of sources.
 *
 * @returns: the number of sources
 *
 **/
guint
hx_player_get_source_count(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetSourceCount(player->player);
}

/**
 * hx_player_get_group_count:
 * @player: a #HXPlayer
 *
 * The number of groups in the playlist.
 *
 * @returns: the number of groups
 *
 **/
guint
hx_player_get_group_count(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetGroupCount(player->player);
}

/**
 * hx_player_get_current_group:
 * @player: a #HXPlayer
 *
 * @returns: the group that is currently playing
 *
 **/
guint
hx_player_get_current_group(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetCurrentGroup(player->player);
}

/**
 * hx_player_set_audio_channels:
 * @player: a #HXPlayer
 * @index: the group
 *
 * Skips to the group at position "index" in the playlist.
 *
 **/
void
hx_player_set_current_group(HXPlayer* player,
                            guint     index)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerSetCurrentGroup(player->player, index);
}

/**
 * hx_player_set_volume
 * @player: a #HXPlayer
 * @volume: the desired volume, from 0 to 100
 *
 * Sets the mixer volume.
 *
 **/
void
hx_player_set_volume(HXPlayer* player,
                     guint     volume)
                         
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerSetVolume(player->player, volume);
}

/**
 * hx_player_get_volume:
 * @player: a #HXPlayer
 *
 * Gets the mixer volume.
 *
 * @returns: the current volume, from 0 to 100
 *
 **/
guint
hx_player_get_volume(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetVolume(player->player);
}

/**
 * hx_player_set_mute:
 * @player: a #HXPlayer
 * @mute: %TRUE to enable mute, %FALSE to disable it
 *
 **/
void
hx_player_set_mute(HXPlayer* player,
                   gboolean  mute)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerMute(player->player, mute);
}

/**
 * hx_player_is_muted:
 * @player: a #HXPlayer
 *
 * @returns: %TRUE if muted, %FALSE if not
 *
 **/
gboolean
hx_player_is_muted(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);
    
    return ClientPlayerIsMuted(player->player);
}

/**
 * hx_player_enable_eq:
 * @player: a #HXPlayer
 * @enable: %TRUE to enable equalizer, %FALSE to disable it
 *
 * By default, the equalizer is disabled. (?)
 *
 **/
void
hx_player_enable_eq(HXPlayer* player,
                    gboolean  enable)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerEnableEQ(player->player, enable);
}

/**
 * hx_player_is_eq_enabled:
 * @player: a #HXPlayer
 *
 * By default, the equalizer is disabled. (?)
 *
 * @returns: whether the equalizer is enabled
 *
 **/
gboolean
hx_player_is_eq_enabled(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    return ClientPlayerIsEQEnabled(player->player);
}

/**
 * hx_player_set_eq_gain_at:
 * @player: a #HXPlayer
 * @band: frequency band to change in Hz (?)
 * @gain: gain to set at the freqency band in dB (?)
 *
 **/
void
hx_player_set_eq_gain_at(HXPlayer* player,
                         guint     band,
                         gint      gain)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerSetEQGain(player->player, band, gain);
}

/**
 * hx_player_get_eq_gain_at:
 * @player: a #HXPlayer
 * @band: frequency band to change in Hz (?)
 *
 * @returns: the gain at the frequency band
 *
 **/
gint
hx_player_get_eq_gain_at(HXPlayer* player,
                         guint     band)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetEQGain(player->player, band);
}

/**
 * hx_player_set_eq_pregain:
 * @player: a #HXPlayer
 * @pregain: the pregain in dB (?)
 *
 **/
void
hx_player_set_eq_pregain(HXPlayer* player,
                         gint      pregain)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerSetEQPreGain(player->player, pregain);
}

/**
 * hx_player_get_eq_pregain:
 * @player: a #HXPlayer
 *
 * @returns: the pregain in dB (?)
 *
 **/
gint
hx_player_get_eq_pregain(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), 0);
    g_return_val_if_fail(player->player != NULL, 0);

    return ClientPlayerGetEQPreGain(player->player);
}

/**
 * hx_player_enable_eq_auto_pregain:
 * @player: a #HXPlayer
 * @enable: %TRUE to have the pregain automatically adjusted
 *
 * By default, automatic pregain adjustment is enabled. (?)
 *
 **/
void
hx_player_enable_eq_auto_pregain(HXPlayer* player,
                                 gboolean  enable)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerEnableEQAutoPreGain(player->player, enable);
}

/**
 * hx_player_is_eq_auto_pregain_enabled:
 * @player: a #HXPlayer
 *
 * @returns: %TRUE if the pregain is automatically adjusted
 *
 **/
gboolean
hx_player_is_eq_auto_pregain_enabled(HXPlayer* player)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(player->player != NULL, FALSE);

    return ClientPlayerIsEQAutoPreGainEnabled(player->player);
}


/**
 * hx_player_set_eq_reverb:
 * @player: a #HXPlayer
 * @room_size: size of the room, in square feet (??)
 * @reverb: degree of sound reflection from the walls in (??)
 *
 * This function applys a reverb effect to the audio.
 * The default reverb settings are ???
 *
 **/
void
hx_player_set_eq_reverb(HXPlayer* player,
                        gint      room_size,
                        gint      reverb)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(player->player != NULL);

    ClientPlayerSetEQReverb(player->player, room_size, reverb);
}

/**
 * hx_player_get_eq_reverb:
 * @player: a #HXPlayer
 * @room_size: size of the room, in square feet (??)
 * @reverb: degree of sound reflection from the walls in (??)
 *
 * Get the current reverb settings.
 *
 **/
void
hx_player_get_eq_reverb(HXPlayer* player,
                        gint*     room_size,
                        gint*     reverb)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(room_size != NULL && reverb != NULL);
    g_return_if_fail(player->player != NULL);

    SInt32 s = 0;
    SInt32 r = 0;
    ClientPlayerGetEQReverb(player->player, &s, &r);
    *room_size = s;
    *reverb = r;
}

static void
hx_player_unrealize(GtkWidget* widget)
{
    HXPlayer* player;

    player = HX_PLAYER(widget);    
    g_assert(player != NULL);

    hx_player_stop(player);

    /* Decrease engine count, and stop engine callback if appropriate */
    g_return_if_fail(g_engine_ref_count > 0);
    
    g_engine_ref_count--;   

#ifdef G_OS_UNIX
    if(g_engine_ref_count == 0)
    {
        gtk_timeout_remove(g_pump_timer_id);

        if(g_dpy)
        {
            XCloseDisplay(g_dpy);
            g_dpy = NULL;
        }
    }

    player->window->window = NULL;
    player->window->display = NULL;
#endif    
}

static void
hx_player_finalize(GObject* object)
{
    HXPlayerClass* klass;
    HXPlayer* player;
    klass = HX_PLAYER_GET_CLASS(object);

    player = HX_PLAYER(object);    

    g_free(player->group_title_buf);
    player->group_title_buf = NULL;

    g_free(player->title_buf);
    player->title_buf = NULL;

    g_free(player->last_opened_url_buf);
    player->last_opened_url_buf = NULL;

    g_players_list = g_list_remove(g_players_list, player);
    
    g_free(player->window);
    player->window = NULL;
}

static void
hx_player_set_property(GObject*      object,
                       guint         prop_id,
                       const GValue* value,
                       GParamSpec*   pspec)
{
    HXPlayer* player;
    
    player = HX_PLAYER(object);
    g_return_if_fail(player != NULL);
    
    switch(prop_id)
    {        
        case PROP_LOOP:
            player->loop = g_value_get_boolean(value);
            break;
            
        case PROP_LOOP_COUNT:
            player->loop_count = g_value_get_uint(value);
            break;
            
        case PROP_SHUFFLE:
            player->shuffle = g_value_get_boolean(value);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break; 
    }
}

static void
hx_player_get_property(GObject*    object,
                       guint       prop_id,
                       GValue*     value,
                       GParamSpec* pspec)
{
    HXPlayer* player;

    player = HX_PLAYER(object);
    g_return_if_fail(player != NULL);
    
    switch(prop_id)
    {        
        case PROP_LOOP_COUNT:
            g_value_set_uint(value, player->loop_count);
            break;
        
        case PROP_SHUFFLE:
            g_value_set_boolean(value, player->shuffle);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break; 
    }
}


static void
hx_player_realize(GtkWidget* widget)
{
    HXPlayer* player;
    GdkWindowAttr attributes;
    gint attributes_mask;
    
    player = HX_PLAYER(widget);

    g_return_if_fail(player != NULL);
    g_return_if_fail(HX_IS_PLAYER(player));

    // Create a window
    GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);

    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.window_type = GDK_WINDOW_CHILD;

    /* The core enables all events when it creates a window,
       so we will too. */
    attributes.event_mask = GDK_ALL_EVENTS_MASK;
    attributes.visual = gtk_widget_get_visual(widget);
    attributes.colormap = gtk_widget_get_colormap(widget);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;


    widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask);

#if defined(G_OS_UNIX)
    gdk_window_add_filter(widget->window,
                          hx_player_event_filter,
                          widget);
    
    widget->style = gtk_style_attach(widget->style, widget->window);

    gdk_window_set_user_data(widget->window, player);

    g_assert(g_dpy != NULL);    
    player->window->display = g_dpy;
    player->window->window = (void*)GDK_DRAWABLE_XID(widget->window);

#elif defined(G_OS_WIN32)
    player->window->window = (void*)GDK_WINDOW_HWND(widget->window);
#else
# error "Unsupported platform"
#endif

    gtk_widget_set_double_buffered(widget, FALSE);
}

static gboolean
hx_player_expose(GtkWidget*      widget,
                 GdkEventExpose* event)
{
    HXPlayer* player;
    
    player = HX_PLAYER(widget);
    
    g_return_val_if_fail(widget != NULL, FALSE); 
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE); 

    
    if (!hx_player_has_visual_content(player) && player->logo_pixmap)
    {
        /* Draw the logo */
        gdk_draw_drawable(widget->window,
                          widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
                          player->logo_pixmap,
                          event->area.x, event->area.y,
                          event->area.x, event->area.y,
                          event->area.width, event->area.height);
    }
    else
    {
        /* Otherwise, the expose event was passed onto
           the core by the event filter */
    }

    return FALSE; // propagate
}

static void
hx_player_size_allocate(GtkWidget*     widget,
                        GtkAllocation* allocation)
{
    HXPlayer* player;
    
    player = HX_PLAYER(widget);
    
    g_return_if_fail(widget != NULL);
    g_return_if_fail(HX_IS_PLAYER(player));
    g_return_if_fail(allocation != NULL);

    widget->allocation =* allocation;

        /* Update the HXxWindow -- used by the hxclientkit */
        player->window->width = allocation->width;
        player->window->height = allocation->height;

        ClientPlayerSetSize(player->player,
                            allocation->width,
                            allocation->height);
        

    if (GTK_WIDGET_REALIZED(widget))
    {
        player = HX_PLAYER(widget);

        gdk_window_move_resize(widget->window,
                               allocation->x, allocation->y,
                               allocation->width, allocation->height);
    }
}

static gboolean
hx_player_motion_notify_event(GtkWidget*      widget,
                              GdkEventMotion* /* event */)
{
    gint x, y;

    /* Acknowedge the motion event */
    gdk_window_get_pointer(widget->window, &x, &y, NULL);

    return FALSE;
}

/**
 * hx_player_set_logo_pixmap:
 * @player: a #HXPlayer
 * @pixmap: the pixmap to use
 *
 * Set the pixmap to be displayed in the widget's location
 * when video is not being played.
 *
 **/
void
hx_player_set_logo_pixmap(HXPlayer*  player,
                          GdkPixmap* pixmap)
{
    if(player->logo_pixmap)
    {
        g_object_unref(G_OBJECT(player->logo_pixmap));
    }

    player->logo_pixmap = pixmap;

    if(pixmap)
    {
        g_object_ref(G_OBJECT(player->logo_pixmap));
        
        gdk_drawable_get_size(GDK_DRAWABLE (pixmap),
                              &player->logo_width,
                              &player->logo_height);
    }
    else
    {
        player->logo_width = 0;
        player->logo_height = 0;
    }

    gtk_widget_queue_resize(GTK_WIDGET(player));
}

/**
 * hx_player_get_title:
 * @player: a #HXPlayer
 *
 * @returns: title of clip being played, or NULL if there
 * is no title
 *
 **/
G_CONST_RETURN gchar*
hx_player_get_title(HXPlayer* player)
{
    const char* szTitle;

    g_return_val_if_fail(player != NULL, NULL);
    
    g_free(player->title_buf);
    player->title_buf = NULL;
    
    szTitle = ClientPlayerGetTitle(player->player);

    if(szTitle)
    {
        if(g_utf8_validate(szTitle, -1, NULL))
        {
            player->title_buf = g_strdup(szTitle);
        }
        else
        {
            gsize in;
            gsize out;
        
            in = strlen(szTitle) + 1;
            out = 0;
            player->title_buf = g_convert(szTitle, in - 1, "UTF-8", "ISO-8859-1", &in, &out, NULL);
        }
    }

    return player->title_buf;
}

/**
 * hx_player_get_group_title:
 * @player: a #HXPlayer
 *
 * @returns: title of group with the given index, or NULL if there
 * is no title
 *
 **/
G_CONST_RETURN gchar*
hx_player_get_group_title(HXPlayer* player,
                          guint     group)
{
    UInt32 buf_desired = 0;
    UInt32 buf_used;
    gchar* buf;
    bool result;

    g_return_val_if_fail(player != NULL, NULL);
    g_return_val_if_fail(HX_IS_PLAYER(player), NULL);
    
    g_free(player->group_title_buf);
    player->group_title_buf = NULL;        

    /* Get the size */
    ClientPlayerGetGroupTitle(player->player, group, NULL, 0, &buf_desired);

    if(buf_desired == 0)
    {
        return NULL;
    }
    
    buf = (gchar*)g_try_malloc(buf_desired + 1);
    g_return_val_if_fail(buf, NULL);
    
    /* Get the data */
    result = ClientPlayerGetGroupTitle(player->player, group, buf, buf_desired, &buf_used);
    if(!result)
    {
        return NULL;
    }

    g_assert(buf_used == buf_desired);

    buf[buf_used] = '\0';

    if(g_utf8_validate(buf, -1, NULL))
    {
        player->group_title_buf = buf;
    }
    else
    {
        /* Convert to utf-8 */
        gsize in;
        gsize out;

        in = strlen(buf) + 1;
        out = 0;
        player->group_title_buf = g_convert(buf, in - 1, "UTF-8", "ISO-8859-1", &in, &out, NULL);

        g_free(buf);
    }
    
    return player->group_title_buf;
}

gboolean
hx_player_get_statistic(HXPlayer*    player,
                        const gchar* key,
                        GValue*      value)
{        
    UInt32 buf_used;
    UInt32 buf_desired = 0;
    int value_type;
    gint int_value;
    gchar* str_value;
    bool result = FALSE;
    gsize in;
    gsize out;
    gchar *utf8_val;

    g_return_val_if_fail(player != NULL, FALSE);
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(key != NULL, FALSE);
    g_return_val_if_fail(value != NULL, FALSE);

    /* Get the size */
    ClientPlayerGetStatistic(player->player, key, NULL, 0, &value_type, &buf_desired);

    if(buf_desired == 0)
    {
        return FALSE;
    }
    
    /* Get the data */
    switch(value_type)
    {
        case kValueType32BitSignedInt:
            result = ClientPlayerGetStatistic(player->player, key, (unsigned char*)&int_value, sizeof(int_value), &value_type, &buf_used);
            if(!result)
            {
                break;
            }
            g_assert(buf_used == buf_desired);
            g_value_init(value, G_TYPE_INT);
            g_value_set_int(value, int_value);
            break;
        case kValueTypeString:
            str_value = (gchar*)g_try_malloc(buf_desired + 1);
            g_return_val_if_fail(str_value != FALSE, FALSE);
            
            result = ClientPlayerGetStatistic(player->player, key, (unsigned char*)str_value, buf_desired, &value_type, &buf_used);
            if(!result)
            {
                break;
            }
            g_assert(buf_used == buf_desired);
            str_value[buf_used] = '\0';

            if(g_utf8_validate(str_value, -1, NULL))
            {
                utf8_val = str_value;
            }
            else
            {
                in = buf_used + 1;
                out = 0;
                utf8_val = g_convert(str_value, buf_used, "UTF-8", "ISO-8859-1", &in, &out, NULL);
                g_assert(utf8_val);
                g_free(str_value);
            }
            
            g_value_init(value, G_TYPE_STRING);
            g_value_set_string_take_ownership(value, utf8_val);
            break;
            
        default:
            g_assert_not_reached();
            break;
    }
    
    return result;
}

#ifdef G_OS_UNIX
static GdkFilterReturn
hx_player_event_filter(GdkXEvent* gxevent,
                       GdkEvent*  /* event */,
                       gpointer   /* widget */)
{
    ClientEngineProcessXEvent((XEvent*)gxevent);
    return GDK_FILTER_CONTINUE;
}

static gboolean
hx_player_pump(gpointer /* unused */)
{
    ClientEngineProcessXEvent(NULL);    

    /* Process any events in the display queue */
    
    if(g_dpy)
    {
        XEvent xevent;
        int x_event_available;

        memset(&xevent, 0, sizeof(xevent));
            
        do
        {
            XLockDisplay(g_dpy);
            x_event_available = XPending(g_dpy);

            if(x_event_available)
            {
                XNextEvent(g_dpy, &xevent);
            }
            XUnlockDisplay(g_dpy);

            if (x_event_available)
            {
                ClientEngineProcessXEvent(&xevent);

                /* RGG: A helpful hint: If you're getting messages here
                   that aren't in the range of the core X events, an
                   X extension may be sending you events. You can look
                   for this extension using the command
                   "xdpyinfo -ext all" and by looking at the reported
                   "base event" number. */
                    
                /* Check the extensions */
                if(g_xshm_present &&
                   (ShmCompletion + g_xshm_event_base) == xevent.type)
                {
                    /* Ignore xshm completion */
                }
                else
                {
                    g_warning("Unhandled event type %d\n", xevent.type);
                }
            }
        } while (x_event_available);
    } 
    
    return TRUE; // do not remove this callback
}
#endif

gboolean
hx_player_authenticate(HXPlayer*    player,
                       gboolean     validate,
                       const gchar* username,
                       const gchar* password)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    g_return_val_if_fail(username != NULL, FALSE);
    g_return_val_if_fail(password != NULL, FALSE);

    return ClientPlayerAuthenticate(player->player, validate, username, password);
}

static void
hx_player_shuffle(HXPlayer* player)
{
    guint group_count = hx_player_get_group_count(player);
    guint group;

    if(group_count > 0)
    {
        group = group_count * (guint)(rand()/(RAND_MAX + 1.0));
        hx_player_set_current_group(player, group);
    }
}

gboolean
hx_player_get_unknown(HXPlayer* player,
                      void**    obj)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    
    return ClientPlayerGetUnknown(player->player, obj);    
}

gboolean
hx_player_get_engine_unknown(HXPlayer* player,
                             void**    obj)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);

    return ClientEngineGetUnknown (obj);
}

HXDataStream*
hx_player_open_data_stream(HXPlayer*    player,
                           const gchar* url,
                           const gchar* mime_type,
                           guint        stream_length,
                           gboolean     autostart)
{
    HXDataStream *stream;

    stream = g_new0(HXDataStream, 1);
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);
    ClientPlayerOpenData(player->player, url, mime_type, stream_length, autostart, stream);

    return stream;
}

gboolean
hx_player_write_data_stream(HXPlayer*     player,
                            HXDataStream* stream,
                            gpointer      data,
                            guint         len)
{
    g_return_val_if_fail(HX_IS_PLAYER(player), FALSE);

    return ClientPlayerWriteData(player->player, *stream, len, (unsigned char*)data);
}

void
hx_player_close_data_stream(HXPlayer*     player,
                            HXDataStream* stream)
{
    g_return_if_fail(HX_IS_PLAYER(player));
    ClientPlayerCloseData(player->player, *stream);

    g_free(stream);
}

void
hx_player_set_status_text(HXPlayer*    player,
                          const gchar* text)
{
    g_return_if_fail(HX_IS_PLAYER(player));

    ClientPlayerSetStatus(player->player, text);
}

gdouble  
hx_player_get_brightness(HXPlayer* player)
{
    float fBrightness = 0.0;

    g_return_val_if_fail(HX_IS_PLAYER(player), fBrightness);

    ClientPlayerGetVideoAttribute(player->player, kVideoAttrBrightness, &fBrightness);

    return (gdouble) fBrightness;
}

gdouble  
hx_player_get_contrast(HXPlayer* player)
{
    float fContrast = 0.0;

    g_return_val_if_fail(HX_IS_PLAYER(player), fContrast);

    ClientPlayerGetVideoAttribute(player->player, kVideoAttrContrast, &fContrast);

    return (gdouble) fContrast;
}

gdouble  
hx_player_get_saturation(HXPlayer* player)
{
    float fSaturation = 0.0;

    g_return_val_if_fail(HX_IS_PLAYER(player), fSaturation);

    ClientPlayerGetVideoAttribute(player->player, kVideoAttrSaturation, &fSaturation);

    return (gdouble) fSaturation;
}

gdouble  
hx_player_get_hue(HXPlayer* player)
{
    float fHue = 0.0;

    g_return_val_if_fail(HX_IS_PLAYER(player), fHue);

    ClientPlayerGetVideoAttribute(player->player, kVideoAttrHue, &fHue);

    return (gdouble) fHue;
}

gdouble  
hx_player_get_sharpness(HXPlayer* player)
{
    float fSharpness = 0.0;

    g_return_val_if_fail(HX_IS_PLAYER(player), fSharpness);

    ClientPlayerGetVideoAttribute(player->player, kVideoAttrSharpness, &fSharpness);

    return (gdouble) fSharpness;
}

gboolean 
hx_player_set_brightness(HXPlayer* player,
                         gdouble   brightness)
{
    bool bResult;
    g_return_val_if_fail(player != NULL, FALSE);

    bResult = ClientPlayerSetVideoAttribute(player->player, 
                                            kVideoAttrBrightness, 
                                            (float) brightness);
    return (gboolean) bResult;
}

gboolean 
hx_player_set_contrast(HXPlayer*      player,
                       gdouble        contrast)
{
    bool bResult;
    g_return_val_if_fail(player != NULL, FALSE);

    bResult = ClientPlayerSetVideoAttribute(player->player, 
                                            kVideoAttrContrast, 
                                            (float) contrast);
    return (gboolean) bResult;
}

gboolean 
hx_player_set_saturation(HXPlayer*      player,
                         gdouble        saturation)
{
    bool bResult;
    g_return_val_if_fail(player != NULL, FALSE);

    bResult = ClientPlayerSetVideoAttribute(player->player, 
                                            kVideoAttrSaturation, 
                                            (float) saturation);
    return (gboolean) bResult;
}

gboolean 
hx_player_set_hue(HXPlayer*      player,
                  gdouble        hue)
{
    bool bResult;
    g_return_val_if_fail(player != NULL, FALSE);

    bResult = ClientPlayerSetVideoAttribute(player->player, 
                                            kVideoAttrHue, 
                                            (float) hue);
    return (gboolean) bResult;
}

gboolean 
hx_player_set_sharpness(HXPlayer*      player,
                        gdouble        sharpness)
{
    bool bResult;
    g_return_val_if_fail(player != NULL, FALSE);

    bResult = ClientPlayerSetVideoAttribute(player->player, 
                                            kVideoAttrSharpness, 
                                            (float) sharpness);
    return (gboolean) bResult;
}
