<commit_before>/*
 * Mex - a media explorer
 *
 * Copyright © 2010, 2011 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>
 */

#include <config.h>

#ifdef USE_PLAYER_CLUTTER_GST
#include <clutter-gst/clutter-gst.h>
#include <mex/mex-media-dbus-bridge.h>
#else
#ifdef USE_PLAYER_DBUS
#include <mex/mex-player-client.h>
#else
#ifdef USE_PLAYER_SURFACE
#include <mex/mex-media-dbus-bridge.h>
#include <mex/mex-surface-player.h>
#else
#error Unexpected player setup
#endif
#endif
#endif

#include "mex-player.h"

#include "mex-main.h"
#include "mex-private.h"
#include "mex-program.h"
#include "mex-content-view.h"
#include "mex-content-tile.h"
#include "mex-media-controls.h"
#include "mex-screensaver.h"
#include "mex-info-panel.h"
static void mex_player_content_view_iface_init (MexContentViewIface *iface);
static void mex_player_focusable_iface_init (MxFocusableIface *iface);

G_DEFINE_TYPE_WITH_CODE (MexPlayer, mex_player, MX_TYPE_STACK,
                         G_IMPLEMENT_INTERFACE (MEX_TYPE_CONTENT_VIEW,
                                                mex_player_content_view_iface_init)
                         G_IMPLEMENT_INTERFACE (MX_TYPE_FOCUSABLE,
                                                mex_player_focusable_iface_init))

#define PLAYER_PRIVATE(o)				      \
	  (G_TYPE_INSTANCE_GET_PRIVATE ((o),		      \
					MEX_TYPE_PLAYER,      \
					MexPlayerPrivate))

struct _MexPlayerPrivate
{
#if defined(USE_PLAYER_CLUTTER_GST) || defined(USE_PLAYER_SURFACE)
  MexMediaDBUSBridge *bridge;
#endif
  ClutterMedia *media;
  MexContent   *content;
  MexModel     *model;

  ClutterActor *controls;
  ClutterActor *related_tile;
  ClutterActor *info_panel;

  guint hide_controls_source;

  guint info_visible : 1;
  guint controls_visible : 1;
  guint controls_prev_visible : 1;

  guint idle_mode : 1;
  guint at_eos : 1;

  gdouble position;
  gdouble current_position;
  guint   duration;

  MexScreensaver *screensaver;
};

enum
{
  CLOSE_REQUEST,

  LAST_SIGNAL
};

enum
{
  PROP_0,

  PROP_IDLE_MODE,
};

static guint signals[LAST_SIGNAL] = { 0, };

static gboolean mex_player_set_controls_visible (MexPlayer *player,
                                                 gboolean   visible);


static void mex_get_stream_cb (MexProgram   *program,
                               const gchar  *url,
                               const GError *error,
                               gpointer      user_data);

static void save_old_content (MexPlayer *player);

static void media_eos_cb (ClutterMedia *media,
                          MexPlayer    *player);

static void media_playing_cb (ClutterMedia *media,
                              GParamSpec   *pspec,
                              MexPlayer    *player);

static void media_update_progress (GObject    *gobject,
                                   GParamSpec *pspec,
                                   MexPlayer  *player);

/* MxFocusable implementation */
static MxFocusable *
mex_player_move_focus (MxFocusable      *focusable,
                       MxFocusDirection  direction,
                       MxFocusable      *old_focus)
{
  if (direction == MX_FOCUS_DIRECTION_UP)
    mex_player_set_controls_visible (MEX_PLAYER (focusable), FALSE);

  return NULL;
}

static void
mex_player_focusable_iface_init (MxFocusableIface *iface)
{
  iface->move_focus = mex_player_move_focus;
}

/* MexContentView implementation */
static void
mex_player_set_content (MexContentView *view,
                        MexContent     *content)
{
  MexPlayerPrivate *priv = MEX_PLAYER (view)->priv;

  if (priv->model)
    mex_media_controls_set_content (MEX_MEDIA_CONTROLS (priv->controls),
                                    content, priv->model);

  if (priv->related_tile)
    {
      g_object_unref (priv->related_tile);
      priv->related_tile = NULL;
    }

  if (content)
    {
      const gchar *sposition, *sduration;

      if (priv->content) {
        save_old_content (MEX_PLAYER (view));
        g_object_unref (priv->content);
        priv->content = NULL;
      }

      priv->content = g_object_ref_sink (content);

      sposition = mex_content_get_metadata (content,
                                            MEX_CONTENT_METADATA_LAST_POSITION);
      sduration = mex_content_get_metadata (content,
                                            MEX_CONTENT_METADATA_DURATION);

      /* TODO: handle special cases like content from a queue, in that
         case we are likely to start from beginning. */
      if (sduration) {
        priv->duration = atoi (sduration);
      } else {
        priv->duration = 0;
      }
      if (priv->duration > 0) {
        if (sposition) {
          int position = atoi (sposition);
          priv->position = (gdouble) position / (gdouble) priv->duration;
        } else {
          priv->position = 0.0;
        }
      }

      if (MEX_IS_PROGRAM (content))
        {
          mex_program_get_stream (MEX_PROGRAM (content),
                                  mex_get_stream_cb,
                                  view);
        }
      else
        {
          const gchar *uri;

          uri = mex_content_get_metadata (content,
                                          MEX_CONTENT_METADATA_STREAM);
          mex_get_stream_cb (NULL, uri, NULL, view);
        }

      if (priv->info_visible)
        {
          clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                 250, "opacity", 0x00, NULL);
          mx_widget_set_disabled (MX_WIDGET (priv->info_panel), TRUE);
          priv->info_visible = FALSE;
        }

      mex_player_set_controls_visible (MEX_PLAYER (view), TRUE);
    }
  else {
    if (priv->content) {
      g_object_unref (priv->content);
      priv->content = NULL;
    }
  }
}

static MexContent *
mex_player_get_content (MexContentView *player)
{
  return MEX_PLAYER (player)->priv->content;
}

static void
mex_player_set_context (MexContentView *player,
                        MexModel *model)
{
  MexPlayerPrivate *priv = MEX_PLAYER (player)->priv;

  if (priv->model)
    g_object_unref (priv->model);

  priv->model = model;

  if (model)
    g_object_ref (model);
}

static MexModel*
mex_player_get_context (MexContentView *view)
{
  return MEX_PLAYER (view)->priv->model;
}

static void
mex_player_content_view_iface_init (MexContentViewIface *iface)
{
  iface->get_content = mex_player_get_content;
  iface->set_content = mex_player_set_content;

  iface->set_context = mex_player_set_context;
  iface->get_context = mex_player_get_context;
}

static void
mex_player_get_property (GObject    *object,
                         guint       property_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
  MexPlayerPrivate *priv = MEX_PLAYER (object)->priv;

  switch (property_id)
    {
    case PROP_IDLE_MODE:
      g_value_set_boolean (value, priv->idle_mode);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mex_player_set_property (GObject      *object,
                         guint         property_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  switch (property_id)
    {
    case PROP_IDLE_MODE:
      mex_player_set_idle_mode (MEX_PLAYER (object),
                                g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mex_player_dispose (GObject *object)
{
  MexPlayer *player = MEX_PLAYER (object);
  MexPlayerPrivate *priv = player->priv;

  if (priv->content)
    {
      g_object_unref (priv->content);
      priv->content = NULL;
    }

  if (priv->model)
    {
      g_object_unref (priv->model);
      priv->model = NULL;
    }

  if (priv->media)
    {
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_eos_cb, player);
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_playing_cb, player);
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_update_progress, player);

      g_object_unref (priv->media);
      priv->media = NULL;
    }

  if (priv->screensaver)
    {
      g_object_unref (priv->screensaver);
      priv->screensaver = NULL;
    }

  if (priv->related_tile)
    {
      g_object_unref (priv->related_tile);
      priv->related_tile = NULL;
    }

#if defined(USE_PLAYER_CLUTTER_GST) || defined(USE_PLAYER_SURFACE)
  if (priv->bridge)
    {
      g_object_unref (priv->bridge);
      priv->bridge = NULL;
    }
#endif

  G_OBJECT_CLASS (mex_player_parent_class)->dispose (object);
}

static void
mex_player_finalize (GObject *object)
{
  G_OBJECT_CLASS (mex_player_parent_class)->finalize (object);
}

static gboolean
mex_hide_controls_cb (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;

  /* timer no longer running */
  priv->hide_controls_source = 0;
  /* if we've timed out then don't bring the controls
     up after the info panel has been visible */
  priv->controls_prev_visible = 0;

  /* hide the controls unless the video is at the end */
  if (!priv->at_eos)
    mex_player_set_controls_visible (player, FALSE);

  return FALSE;
}

static void
mex_player_restart_timer (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (priv->hide_controls_source)
    g_source_remove (priv->hide_controls_source);

  priv->hide_controls_source =
    g_timeout_add_seconds (12, (GSourceFunc) mex_hide_controls_cb, player);
}


static gboolean
mex_player_captured_event (ClutterActor    *actor,
                           ClutterEvent *event)
{
  MexPlayerPrivate *priv = MEX_PLAYER (actor)->priv;

  if (!priv->controls_prev_visible)
    mex_player_restart_timer (MEX_PLAYER (actor));

  return FALSE;
}

static gboolean
mex_player_key_press_event (ClutterActor    *actor,
                            ClutterKeyEvent *event)
{
  MexPlayerPrivate *priv = MEX_PLAYER (actor)->priv;
  ClutterStage *stage;
  MxFocusManager *fmanager;

  stage = (ClutterStage*) clutter_actor_get_stage (actor);
  fmanager = mx_focus_manager_get_for_stage (stage);

  switch (event->keyval)
    {
    case CLUTTER_KEY_Down:
        {
          if (!priv->controls_visible && !priv->info_visible)
            return mex_player_set_controls_visible (MEX_PLAYER (actor), TRUE);
          break;
        }

    case MEX_KEY_INFO:
        {
          MexContent *content;
          ClutterActor *controls;

          controls = priv->controls;
          content = priv->content;

          if (priv->info_visible)
            {
              /* hide the info panel */
              clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                     250, "opacity", 0x00, NULL);

              mx_widget_set_disabled (MX_WIDGET (priv->info_panel), TRUE);
              mx_widget_set_disabled (MX_WIDGET (priv->controls), FALSE);

              priv->info_visible = FALSE;

              if (priv->controls_prev_visible)
                mex_player_set_controls_visible (MEX_PLAYER (actor), TRUE);
            }
          else
            {
              /* if you're pressing info button while the media controls are up
               set them as previously visible */
              if (priv->controls_visible)
                priv->controls_prev_visible = TRUE;

              MxFocusable *focusable;
              focusable = mx_focus_manager_get_focused (fmanager);
              if (MEX_IS_CONTENT_TILE (focusable) &&
                  priv->controls_prev_visible == TRUE)
                {
                  content =
                    mex_content_view_get_content (MEX_CONTENT_VIEW (focusable));

                  /* to avoid any accidental leak */
                  if (priv->related_tile)
                    {
                      g_object_unref (priv->related_tile);
                      priv->related_tile = NULL;
                    }
                  priv->related_tile = g_object_ref (focusable);
                }

              mex_content_view_set_content (MEX_CONTENT_VIEW (priv->info_panel),
                                            content);

              /* show the info panel */
              clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                     250, "opacity", 0xff, NULL);

              mx_widget_set_disabled (MX_WIDGET (priv->info_panel), FALSE);
              mx_widget_set_disabled (MX_WIDGET (priv->controls), TRUE);

              priv->info_visible = TRUE;

              mex_player_set_controls_visible (MEX_PLAYER (actor), FALSE);

              mex_push_focus (MX_FOCUSABLE (priv->info_panel));
            }

          return TRUE;
        }
    }

  /* any other key presses should reset the hide timer */
  mex_player_restart_timer (MEX_PLAYER (actor));

  return FALSE;
}

static void
mex_player_class_init (MexPlayerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
  GParamSpec *pspec;

  g_type_class_add_private (klass, sizeof (MexPlayerPrivate));

  object_class->get_property = mex_player_get_property;
  object_class->set_property = mex_player_set_property;
  object_class->dispose = mex_player_dispose;
  object_class->finalize = mex_player_finalize;

  actor_class->key_press_event = mex_player_key_press_event;
  actor_class->captured_event = mex_player_captured_event;

  signals[CLOSE_REQUEST] = g_signal_new ("close-request",
                                         G_TYPE_FROM_CLASS (klass),
                                         G_SIGNAL_RUN_LAST,
                                         0, NULL, NULL,
                                         g_cclosure_marshal_VOID__VOID,
                                         G_TYPE_NONE, 0);

  pspec = g_param_spec_boolean ("idle-mode",
                                "Idle Mode",
                                "Idle Mode",
                                TRUE,
                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_IDLE_MODE, pspec);
}

static gboolean
mex_player_set_controls_visible (MexPlayer *player,
                                 gboolean   visible)
{
  MexPlayerPrivate *priv = player->priv;
  gfloat pos;
  ClutterStage *stage;
  MxFocusManager *fmanager;

  stage = (ClutterStage*) clutter_actor_get_stage (CLUTTER_ACTOR (player));
  fmanager = mx_focus_manager_get_for_stage (stage);

  pos = clutter_actor_get_height (priv->controls);
  if (visible)
    {
      priv->controls_prev_visible = FALSE;
      priv->controls_visible = TRUE;

      mx_widget_set_disabled (MX_WIDGET (priv->controls), FALSE);

      clutter_actor_animate (priv->controls, CLUTTER_EASE_IN_SINE, 250,
                             "opacity", 0xff,
                             "anchor-y", 0.0,
                             NULL);

      mex_player_restart_timer (player);

      if (priv->related_tile)
        {
          mx_focus_manager_push_focus_with_hint (fmanager,
                                                 MX_FOCUSABLE (priv->related_tile),
                                                 MX_FOCUS_HINT_PRIOR);

          g_object_unref (priv->related_tile);
          priv->related_tile = NULL;
        }
      else
        {
          mx_focus_manager_push_focus (fmanager, MX_FOCUSABLE (priv->controls));
        }
    }

  if (!visible)
    {
      priv->controls_visible = FALSE;
      clutter_actor_animate (priv->controls, CLUTTER_EASE_IN_SINE, 250,
                             "opacity", 0x00,
                             "anchor-y", -pos,
                             NULL);

      if (priv->hide_controls_source)
        {
          g_source_remove (priv->hide_controls_source);
          priv->hide_controls_source = 0;
        }
    }

  return TRUE;
}

static void
controls_stopped_cb (MexMediaControls *controls,
                     MexPlayer        *player)
{

  if (player->priv->hide_controls_source)
    {
      g_source_remove (player->priv->hide_controls_source);
      player->priv->hide_controls_source = 0;
    }

  g_signal_emit (player, signals[CLOSE_REQUEST], 0);
}

static void
media_eos_cb (ClutterMedia *media,
              MexPlayer    *player)
{
  MexPlayerPrivate *priv = player->priv;

  priv->position = 0.0;

  if (priv->idle_mode)
    {
      /* loop the video in idle mode */
      clutter_media_set_progress (media, priv->position);
      clutter_media_set_playing (media, TRUE);
    }
  else
    {
      /* set the control visible */
      mex_player_set_controls_visible (player, TRUE);

      clutter_media_set_progress (media, priv->position);
      clutter_media_set_playing (media, FALSE);

      /* focus the related content */
      mex_media_controls_focus_content (MEX_MEDIA_CONTROLS (priv->controls),
                                        priv->content);

      /* we're not playing the content or playing the idle video so allow
       * screensaver if previously inhibited */
      mex_screensaver_uninhibit (priv->screensaver);

      priv->current_position = 0.0;
      priv->at_eos = TRUE;
    }
}

static void
media_playing_cb (ClutterMedia *media,
                  GParamSpec   *pspec,
                  MexPlayer    *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (!priv->idle_mode)
    mex_screensaver_inhibit (priv->screensaver);

  /* reset at_eos flag if a video is now playing */
  if (clutter_media_get_playing (media))
    priv->at_eos = FALSE;
}

static void
save_old_content (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;
  guint position;
  gchar str[20];

  if (priv->duration &&
      mex_generic_content_get_save_last_position (MEX_GENERIC_CONTENT (priv->content))) {
    priv->position = priv->current_position;
    position = (guint) (priv->position * priv->duration);
    snprintf (str, sizeof (str), "%u", position);
    mex_content_set_metadata (priv->content,
                              MEX_CONTENT_METADATA_LAST_POSITION,
                              str);
  }

  mex_content_set_last_used_metadatas (priv->content);
  mex_content_save_metadata (priv->content);
}

static void
media_update_progress (GObject    *gobject,
                       GParamSpec *pspec,
                       MexPlayer  *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (!priv->at_eos)
    priv->current_position = clutter_media_get_progress (priv->media);
}

static void
mex_player_init (MexPlayer *self)
{
  MexPlayerPrivate *priv;

  self->priv = priv = PLAYER_PRIVATE (self);

#ifdef USE_PLAYER_CLUTTER_GST
  priv->media = (ClutterMedia *) clutter_gst_video_texture_new ();

  /* We want to keep a reference to the media here to ensure consistency with
   * the D-BUS client interface behaviour
   */<commit_msg>player: Make sure the restore playing metadata string is not set to "0"<commit_after>/*
 * Mex - a media explorer
 *
 * Copyright © 2010, 2011 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>
 */

#include <config.h>

#ifdef USE_PLAYER_CLUTTER_GST
#include <clutter-gst/clutter-gst.h>
#include <mex/mex-media-dbus-bridge.h>
#else
#ifdef USE_PLAYER_DBUS
#include <mex/mex-player-client.h>
#else
#ifdef USE_PLAYER_SURFACE
#include <mex/mex-media-dbus-bridge.h>
#include <mex/mex-surface-player.h>
#else
#error Unexpected player setup
#endif
#endif
#endif

#include "mex-player.h"

#include "mex-main.h"
#include "mex-private.h"
#include "mex-program.h"
#include "mex-content-view.h"
#include "mex-content-tile.h"
#include "mex-media-controls.h"
#include "mex-screensaver.h"
#include "mex-info-panel.h"
static void mex_player_content_view_iface_init (MexContentViewIface *iface);
static void mex_player_focusable_iface_init (MxFocusableIface *iface);

G_DEFINE_TYPE_WITH_CODE (MexPlayer, mex_player, MX_TYPE_STACK,
                         G_IMPLEMENT_INTERFACE (MEX_TYPE_CONTENT_VIEW,
                                                mex_player_content_view_iface_init)
                         G_IMPLEMENT_INTERFACE (MX_TYPE_FOCUSABLE,
                                                mex_player_focusable_iface_init))

#define PLAYER_PRIVATE(o)				      \
	  (G_TYPE_INSTANCE_GET_PRIVATE ((o),		      \
					MEX_TYPE_PLAYER,      \
					MexPlayerPrivate))

struct _MexPlayerPrivate
{
#if defined(USE_PLAYER_CLUTTER_GST) || defined(USE_PLAYER_SURFACE)
  MexMediaDBUSBridge *bridge;
#endif
  ClutterMedia *media;
  MexContent   *content;
  MexModel     *model;

  ClutterActor *controls;
  ClutterActor *related_tile;
  ClutterActor *info_panel;

  guint hide_controls_source;

  guint info_visible : 1;
  guint controls_visible : 1;
  guint controls_prev_visible : 1;

  guint idle_mode : 1;
  guint at_eos : 1;

  gdouble position;
  gdouble current_position;
  guint   duration;

  MexScreensaver *screensaver;
};

enum
{
  CLOSE_REQUEST,

  LAST_SIGNAL
};

enum
{
  PROP_0,

  PROP_IDLE_MODE,
};

static guint signals[LAST_SIGNAL] = { 0, };

static gboolean mex_player_set_controls_visible (MexPlayer *player,
                                                 gboolean   visible);


static void mex_get_stream_cb (MexProgram   *program,
                               const gchar  *url,
                               const GError *error,
                               gpointer      user_data);

static void save_old_content (MexPlayer *player);

static void media_eos_cb (ClutterMedia *media,
                          MexPlayer    *player);

static void media_playing_cb (ClutterMedia *media,
                              GParamSpec   *pspec,
                              MexPlayer    *player);

static void media_update_progress (GObject    *gobject,
                                   GParamSpec *pspec,
                                   MexPlayer  *player);

/* MxFocusable implementation */
static MxFocusable *
mex_player_move_focus (MxFocusable      *focusable,
                       MxFocusDirection  direction,
                       MxFocusable      *old_focus)
{
  if (direction == MX_FOCUS_DIRECTION_UP)
    mex_player_set_controls_visible (MEX_PLAYER (focusable), FALSE);

  return NULL;
}

static void
mex_player_focusable_iface_init (MxFocusableIface *iface)
{
  iface->move_focus = mex_player_move_focus;
}

/* MexContentView implementation */
static void
mex_player_set_content (MexContentView *view,
                        MexContent     *content)
{
  MexPlayerPrivate *priv = MEX_PLAYER (view)->priv;

  if (priv->model)
    mex_media_controls_set_content (MEX_MEDIA_CONTROLS (priv->controls),
                                    content, priv->model);

  if (priv->related_tile)
    {
      g_object_unref (priv->related_tile);
      priv->related_tile = NULL;
    }

  if (content)
    {
      const gchar *sposition, *sduration;

      if (priv->content) {
        save_old_content (MEX_PLAYER (view));
        g_object_unref (priv->content);
        priv->content = NULL;
      }

      priv->content = g_object_ref_sink (content);

      sposition = mex_content_get_metadata (content,
                                            MEX_CONTENT_METADATA_LAST_POSITION);
      sduration = mex_content_get_metadata (content,
                                            MEX_CONTENT_METADATA_DURATION);

      /* TODO: handle special cases like content from a queue, in that
         case we are likely to start from beginning. */
      if (sduration) {
        priv->duration = atoi (sduration);
      } else {
        priv->duration = 0;
      }
      if (priv->duration > 0) {
        if (sposition) {
          int position = atoi (sposition);
          priv->position = (gdouble) position / (gdouble) priv->duration;
        } else {
          priv->position = 0.0;
        }
      }

      if (MEX_IS_PROGRAM (content))
        {
          mex_program_get_stream (MEX_PROGRAM (content),
                                  mex_get_stream_cb,
                                  view);
        }
      else
        {
          const gchar *uri;

          uri = mex_content_get_metadata (content,
                                          MEX_CONTENT_METADATA_STREAM);
          mex_get_stream_cb (NULL, uri, NULL, view);
        }

      if (priv->info_visible)
        {
          clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                 250, "opacity", 0x00, NULL);
          mx_widget_set_disabled (MX_WIDGET (priv->info_panel), TRUE);
          priv->info_visible = FALSE;
        }

      mex_player_set_controls_visible (MEX_PLAYER (view), TRUE);
    }
  else {
    if (priv->content) {
      g_object_unref (priv->content);
      priv->content = NULL;
    }
  }
}

static MexContent *
mex_player_get_content (MexContentView *player)
{
  return MEX_PLAYER (player)->priv->content;
}

static void
mex_player_set_context (MexContentView *player,
                        MexModel *model)
{
  MexPlayerPrivate *priv = MEX_PLAYER (player)->priv;

  if (priv->model)
    g_object_unref (priv->model);

  priv->model = model;

  if (model)
    g_object_ref (model);
}

static MexModel*
mex_player_get_context (MexContentView *view)
{
  return MEX_PLAYER (view)->priv->model;
}

static void
mex_player_content_view_iface_init (MexContentViewIface *iface)
{
  iface->get_content = mex_player_get_content;
  iface->set_content = mex_player_set_content;

  iface->set_context = mex_player_set_context;
  iface->get_context = mex_player_get_context;
}

static void
mex_player_get_property (GObject    *object,
                         guint       property_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
  MexPlayerPrivate *priv = MEX_PLAYER (object)->priv;

  switch (property_id)
    {
    case PROP_IDLE_MODE:
      g_value_set_boolean (value, priv->idle_mode);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mex_player_set_property (GObject      *object,
                         guint         property_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  switch (property_id)
    {
    case PROP_IDLE_MODE:
      mex_player_set_idle_mode (MEX_PLAYER (object),
                                g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mex_player_dispose (GObject *object)
{
  MexPlayer *player = MEX_PLAYER (object);
  MexPlayerPrivate *priv = player->priv;

  if (priv->content)
    {
      g_object_unref (priv->content);
      priv->content = NULL;
    }

  if (priv->model)
    {
      g_object_unref (priv->model);
      priv->model = NULL;
    }

  if (priv->media)
    {
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_eos_cb, player);
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_playing_cb, player);
      g_signal_handlers_disconnect_by_func (priv->media,
                                            media_update_progress, player);

      g_object_unref (priv->media);
      priv->media = NULL;
    }

  if (priv->screensaver)
    {
      g_object_unref (priv->screensaver);
      priv->screensaver = NULL;
    }

  if (priv->related_tile)
    {
      g_object_unref (priv->related_tile);
      priv->related_tile = NULL;
    }

#if defined(USE_PLAYER_CLUTTER_GST) || defined(USE_PLAYER_SURFACE)
  if (priv->bridge)
    {
      g_object_unref (priv->bridge);
      priv->bridge = NULL;
    }
#endif

  G_OBJECT_CLASS (mex_player_parent_class)->dispose (object);
}

static void
mex_player_finalize (GObject *object)
{
  G_OBJECT_CLASS (mex_player_parent_class)->finalize (object);
}

static gboolean
mex_hide_controls_cb (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;

  /* timer no longer running */
  priv->hide_controls_source = 0;
  /* if we've timed out then don't bring the controls
     up after the info panel has been visible */
  priv->controls_prev_visible = 0;

  /* hide the controls unless the video is at the end */
  if (!priv->at_eos)
    mex_player_set_controls_visible (player, FALSE);

  return FALSE;
}

static void
mex_player_restart_timer (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (priv->hide_controls_source)
    g_source_remove (priv->hide_controls_source);

  priv->hide_controls_source =
    g_timeout_add_seconds (12, (GSourceFunc) mex_hide_controls_cb, player);
}


static gboolean
mex_player_captured_event (ClutterActor    *actor,
                           ClutterEvent *event)
{
  MexPlayerPrivate *priv = MEX_PLAYER (actor)->priv;

  if (!priv->controls_prev_visible)
    mex_player_restart_timer (MEX_PLAYER (actor));

  return FALSE;
}

static gboolean
mex_player_key_press_event (ClutterActor    *actor,
                            ClutterKeyEvent *event)
{
  MexPlayerPrivate *priv = MEX_PLAYER (actor)->priv;
  ClutterStage *stage;
  MxFocusManager *fmanager;

  stage = (ClutterStage*) clutter_actor_get_stage (actor);
  fmanager = mx_focus_manager_get_for_stage (stage);

  switch (event->keyval)
    {
    case CLUTTER_KEY_Down:
        {
          if (!priv->controls_visible && !priv->info_visible)
            return mex_player_set_controls_visible (MEX_PLAYER (actor), TRUE);
          break;
        }

    case MEX_KEY_INFO:
        {
          MexContent *content;
          ClutterActor *controls;

          controls = priv->controls;
          content = priv->content;

          if (priv->info_visible)
            {
              /* hide the info panel */
              clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                     250, "opacity", 0x00, NULL);

              mx_widget_set_disabled (MX_WIDGET (priv->info_panel), TRUE);
              mx_widget_set_disabled (MX_WIDGET (priv->controls), FALSE);

              priv->info_visible = FALSE;

              if (priv->controls_prev_visible)
                mex_player_set_controls_visible (MEX_PLAYER (actor), TRUE);
            }
          else
            {
              /* if you're pressing info button while the media controls are up
               set them as previously visible */
              if (priv->controls_visible)
                priv->controls_prev_visible = TRUE;

              MxFocusable *focusable;
              focusable = mx_focus_manager_get_focused (fmanager);
              if (MEX_IS_CONTENT_TILE (focusable) &&
                  priv->controls_prev_visible == TRUE)
                {
                  content =
                    mex_content_view_get_content (MEX_CONTENT_VIEW (focusable));

                  /* to avoid any accidental leak */
                  if (priv->related_tile)
                    {
                      g_object_unref (priv->related_tile);
                      priv->related_tile = NULL;
                    }
                  priv->related_tile = g_object_ref (focusable);
                }

              mex_content_view_set_content (MEX_CONTENT_VIEW (priv->info_panel),
                                            content);

              /* show the info panel */
              clutter_actor_animate (priv->info_panel, CLUTTER_EASE_IN_SINE,
                                     250, "opacity", 0xff, NULL);

              mx_widget_set_disabled (MX_WIDGET (priv->info_panel), FALSE);
              mx_widget_set_disabled (MX_WIDGET (priv->controls), TRUE);

              priv->info_visible = TRUE;

              mex_player_set_controls_visible (MEX_PLAYER (actor), FALSE);

              mex_push_focus (MX_FOCUSABLE (priv->info_panel));
            }

          return TRUE;
        }
    }

  /* any other key presses should reset the hide timer */
  mex_player_restart_timer (MEX_PLAYER (actor));

  return FALSE;
}

static void
mex_player_class_init (MexPlayerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
  GParamSpec *pspec;

  g_type_class_add_private (klass, sizeof (MexPlayerPrivate));

  object_class->get_property = mex_player_get_property;
  object_class->set_property = mex_player_set_property;
  object_class->dispose = mex_player_dispose;
  object_class->finalize = mex_player_finalize;

  actor_class->key_press_event = mex_player_key_press_event;
  actor_class->captured_event = mex_player_captured_event;

  signals[CLOSE_REQUEST] = g_signal_new ("close-request",
                                         G_TYPE_FROM_CLASS (klass),
                                         G_SIGNAL_RUN_LAST,
                                         0, NULL, NULL,
                                         g_cclosure_marshal_VOID__VOID,
                                         G_TYPE_NONE, 0);

  pspec = g_param_spec_boolean ("idle-mode",
                                "Idle Mode",
                                "Idle Mode",
                                TRUE,
                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_IDLE_MODE, pspec);
}

static gboolean
mex_player_set_controls_visible (MexPlayer *player,
                                 gboolean   visible)
{
  MexPlayerPrivate *priv = player->priv;
  gfloat pos;
  ClutterStage *stage;
  MxFocusManager *fmanager;

  stage = (ClutterStage*) clutter_actor_get_stage (CLUTTER_ACTOR (player));
  fmanager = mx_focus_manager_get_for_stage (stage);

  pos = clutter_actor_get_height (priv->controls);
  if (visible)
    {
      priv->controls_prev_visible = FALSE;
      priv->controls_visible = TRUE;

      mx_widget_set_disabled (MX_WIDGET (priv->controls), FALSE);

      clutter_actor_animate (priv->controls, CLUTTER_EASE_IN_SINE, 250,
                             "opacity", 0xff,
                             "anchor-y", 0.0,
                             NULL);

      mex_player_restart_timer (player);

      if (priv->related_tile)
        {
          mx_focus_manager_push_focus_with_hint (fmanager,
                                                 MX_FOCUSABLE (priv->related_tile),
                                                 MX_FOCUS_HINT_PRIOR);

          g_object_unref (priv->related_tile);
          priv->related_tile = NULL;
        }
      else
        {
          mx_focus_manager_push_focus (fmanager, MX_FOCUSABLE (priv->controls));
        }
    }

  if (!visible)
    {
      priv->controls_visible = FALSE;
      clutter_actor_animate (priv->controls, CLUTTER_EASE_IN_SINE, 250,
                             "opacity", 0x00,
                             "anchor-y", -pos,
                             NULL);

      if (priv->hide_controls_source)
        {
          g_source_remove (priv->hide_controls_source);
          priv->hide_controls_source = 0;
        }
    }

  return TRUE;
}

static void
controls_stopped_cb (MexMediaControls *controls,
                     MexPlayer        *player)
{

  if (player->priv->hide_controls_source)
    {
      g_source_remove (player->priv->hide_controls_source);
      player->priv->hide_controls_source = 0;
    }

  g_signal_emit (player, signals[CLOSE_REQUEST], 0);
}

static void
media_eos_cb (ClutterMedia *media,
              MexPlayer    *player)
{
  MexPlayerPrivate *priv = player->priv;

  priv->position = 0.0;

  if (priv->idle_mode)
    {
      /* loop the video in idle mode */
      clutter_media_set_progress (media, priv->position);
      clutter_media_set_playing (media, TRUE);
    }
  else
    {
      /* set the control visible */
      mex_player_set_controls_visible (player, TRUE);

      clutter_media_set_progress (media, priv->position);
      clutter_media_set_playing (media, FALSE);

      /* focus the related content */
      mex_media_controls_focus_content (MEX_MEDIA_CONTROLS (priv->controls),
                                        priv->content);

      /* we're not playing the content or playing the idle video so allow
       * screensaver if previously inhibited */
      mex_screensaver_uninhibit (priv->screensaver);

      priv->current_position = 0.0;
      priv->at_eos = TRUE;
    }
}

static void
media_playing_cb (ClutterMedia *media,
                  GParamSpec   *pspec,
                  MexPlayer    *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (!priv->idle_mode)
    mex_screensaver_inhibit (priv->screensaver);

  /* reset at_eos flag if a video is now playing */
  if (clutter_media_get_playing (media))
    priv->at_eos = FALSE;
}

static void
save_old_content (MexPlayer *player)
{
  MexPlayerPrivate *priv = player->priv;
  guint position;
  gchar str[20];

  if (priv->duration &&
      mex_generic_content_get_save_last_position (MEX_GENERIC_CONTENT (priv->content))) {
    priv->position = priv->current_position;
    position = (guint) (priv->position * priv->duration);
    if (position > 0)
      {
        snprintf (str, sizeof (str), "%u", position);
        mex_content_set_metadata (priv->content,
                                  MEX_CONTENT_METADATA_LAST_POSITION,
                                  str);
      }
  }

  mex_content_set_last_used_metadatas (priv->content);
  mex_content_save_metadata (priv->content);
}

static void
media_update_progress (GObject    *gobject,
                       GParamSpec *pspec,
                       MexPlayer  *player)
{
  MexPlayerPrivate *priv = player->priv;

  if (!priv->at_eos)
    priv->current_position = clutter_media_get_progress (priv->media);
}

static void
mex_player_init (MexPlayer *self)
{
  MexPlayerPrivate *priv;

  self->priv = priv = PLAYER_PRIVATE (self);

#ifdef USE_PLAYER_CLUTTER_GST
  priv->media = (ClutterMedia *) clutter_gst_video_texture_new ();

  /* We want to keep a reference to the media here to ensure consistency with
   * the D-BUS client interface behaviour
   */
{'fraction_non_alphanumeric': 0.08782231299463876, 'fraction_numerical': 0.0033188664794485574, 'mean_word_length': 2.172511541265085, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 332, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 94, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1151791', 'n_tokens_mistral': 12351, 'n_tokens_neox': 11865, 'n_words': 2483}