/**
* GMyth Library
*
* @file gmyth/mmyth_tvplayer.c
*
* @brief
This component provides playback of the remote A/V using
* GStreamer.
*
* Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia.
* @author Hallyson Luiz de Morais Melo
*
*//*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "mmyth_tvplayer.h"
#include
#include
#define MYTHTV_VERSION_DEFAULT 30
typedef struct _GstPlayerWindowStateChange {
GstElement *play;
GstState old_state,
new_state;
MMythTVPlayer *tvplayer;
} GstPlayerWindowStateChange;
typedef struct _GstPlayerWindowTagFound {
GstElement *play;
GstTagList *taglist;
MMythTVPlayer *tvplayer;
} GstPlayerWindowTagFound;
/*
* static gboolean idle_state (gpointer data);
*/
static gboolean bus_call(GstBus * bus, GstMessage * msg, gpointer data);
static void mmyth_tvplayer_class_init(MMythTVPlayerClass * klass);
static void mmyth_tvplayer_init(MMythTVPlayer * object);
static void mmyth_tvplayer_dispose(GObject * object);
static void mmyth_tvplayer_finalize(GObject * object);
G_DEFINE_TYPE(MMythTVPlayer, mmyth_tvplayer, G_TYPE_OBJECT)
static gboolean mmyth_tvplayer_create_pipeline(MMythTVPlayer *
tvplayer);
static void new_pad_cb(GstElement * element, GstPad * pad,
gpointer data);
static gboolean expose_cb(GtkWidget * widget,
GdkEventExpose * event, gpointer user_data);
static void
mmyth_tvplayer_class_init(MMythTVPlayerClass * klass)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) klass;
gobject_class->dispose = mmyth_tvplayer_dispose;
gobject_class->finalize = mmyth_tvplayer_finalize;
}
static void
new_pad_cb(GstElement * element, GstPad * pad, gpointer data)
{
MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(data);
GstPadLinkReturn ret;
char *s;
s = gst_caps_to_string(pad->caps);
if (s[0] == 'a') {
ret =
gst_pad_link(pad,
gst_element_get_pad(tvplayer->audioqueue1,
"sink"));
} else {
ret =
gst_pad_link(pad,
gst_element_get_pad(tvplayer->videoqueue1,
"sink"));
}
g_free(s);
}
static gboolean
expose_cb(GtkWidget * widget, GdkEventExpose * event, gpointer user_data)
{
MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(user_data);
if (tvplayer && tvplayer->videow) {
gst_x_overlay_set_xwindow_id(GST_X_OVERLAY
(tvplayer->gst_videosink),
GDK_WINDOW_XWINDOW(widget->window));
return TRUE;
}
g_warning("MMythTVPlayer expose called before setting video window\n");
return FALSE;
}
static void
mmyth_tvplayer_init(MMythTVPlayer * tvplayer)
{
tvplayer->gst_pipeline = NULL;
tvplayer->gst_source = NULL;
tvplayer->gst_videodec = NULL;
tvplayer->gst_videosink = NULL;
tvplayer->gst_videocolortrs = NULL;
tvplayer->videoqueue1 = NULL;
tvplayer->videoqueue2 = NULL;
tvplayer->audioqueue1 = NULL;
tvplayer->audioqueue2 = NULL;
/*
* GTKWidget for rendering the video
*/
tvplayer->videow = NULL;
tvplayer->expose_handler = 0;
tvplayer->backend_hostname = NULL;
tvplayer->backend_port = 0;
tvplayer->local_hostname = NULL;
tvplayer->recorder = NULL;
tvplayer->tvchain = NULL;
tvplayer->proginfo = NULL;
}
static void
mmyth_tvplayer_dispose(GObject * object)
{
G_OBJECT_CLASS(mmyth_tvplayer_parent_class)->dispose(object);
}
static void
mmyth_tvplayer_finalize(GObject * object)
{
g_signal_handlers_destroy(object);
MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(object);
g_debug("[%s] Finalizing tvplayer", __FUNCTION__);
if (tvplayer->videow != NULL) {
if (g_signal_handler_is_connected(tvplayer->videow,
tvplayer->expose_handler)) {
g_signal_handler_disconnect(tvplayer->videow,
tvplayer->expose_handler);
}
g_object_unref(tvplayer->videow);
}
if (tvplayer->recorder != NULL)
g_object_unref(tvplayer->recorder);
if (tvplayer->tvchain != NULL)
g_object_unref(tvplayer->tvchain);
if (tvplayer->proginfo != NULL)
g_object_unref(tvplayer->proginfo);
// Release Gstreamer elements
if (tvplayer->gst_pipeline != NULL)
g_object_unref(tvplayer->gst_pipeline);
if (tvplayer->gst_source != NULL)
g_object_unref(tvplayer->gst_source);
if (tvplayer->gst_videodec != NULL)
g_object_unref(tvplayer->gst_videodec);
if (tvplayer->gst_videocolortrs != NULL)
g_object_unref(tvplayer->gst_videocolortrs);
if (tvplayer->gst_videosink != NULL)
g_object_unref(tvplayer->gst_videosink);
if (tvplayer->videoqueue1 != NULL)
g_object_unref(tvplayer->videoqueue1);
if (tvplayer->videoqueue2 != NULL)
g_object_unref(tvplayer->videoqueue2);
if (tvplayer->audioqueue1 != NULL)
g_object_unref(tvplayer->audioqueue1);
if (tvplayer->audioqueue2 != NULL)
g_object_unref(tvplayer->audioqueue2);
G_OBJECT_CLASS(mmyth_tvplayer_parent_class)->finalize(object);
}
/** Creates a new instance of MMythTVPlayer.
*
* @return a new instance of MMythTVPlayer.
*/
MMythTVPlayer *
mmyth_tvplayer_new()
{
MMythTVPlayer *tvplayer =
MMYTH_TVPLAYER(g_object_new(MMYTH_TVPLAYER_TYPE, NULL));
return tvplayer;
}
/** Initializes the tv player.
*
* @param tvplayer the object instance.
* @return gboolean TRUE if the pipeline was created
* successfully, FALSE otherwise.
*/
gboolean
mmyth_tvplayer_initialize(MMythTVPlayer * tvplayer,
GMythBackendInfo * backend_info)
{
tvplayer->backend_info = backend_info;
if (!mmyth_tvplayer_create_pipeline(tvplayer)) {
g_warning
("[%s] Error while creating pipeline. TV Player not initialized",
__FUNCTION__);
return FALSE;
} else {
g_debug("[%s] GStreamer pipeline created", __FUNCTION__);
}
return TRUE;
}
/** Creates the GStreamer pipeline used by the player.
*
* @param tvplayer the object instance.
* @return gboolean TRUE if the pipeline was created
* successfully, FALSE otherwise.
*/
static gboolean
mmyth_tvplayer_create_pipeline(MMythTVPlayer * tvplayer)
{
GstElement *pipeline;
GstElement *source,
*parser;
GstElement *videodec,
*videosink;
GstElement *videocolortrs;
#ifndef MAEMO_PLATFORM
GstElement *audiodec,
*audioconv,
*audioqueue2;
#endif
GstElement *audiosink;
GstElement *videoqueue1,
*videoqueue2,
*audioqueue1;
g_debug("MMythTVPlayer: Setting the Gstreamer pipeline\n");
pipeline = gst_pipeline_new("video-player");
source = gst_element_factory_make("mythtvsrc", "myth-source");
parser = gst_element_factory_make("nuvdemux", "nuv-demux");
/*
* Gstreamer Video elements
*/
videoqueue1 = gst_element_factory_make("queue", "video-queue1");
videodec = gst_element_factory_make("ffdec_mpeg4", "video-decoder");
videoqueue2 = gst_element_factory_make("queue", "video-queue2");
videocolortrs =
gst_element_factory_make("ffmpegcolorspace",
"image-color-transforms");
#ifdef MAEMO_PLATFORM
videosink = gst_element_factory_make("sdlvideosink", "image-output");
#else
videosink = gst_element_factory_make("xvimagesink", "image-output");
#endif
/*
* Gstreamer Audio elements
*/
audioqueue1 = gst_element_factory_make("queue", "audio-queue1");
#ifdef MAEMO_PLATFORM
audiosink = gst_element_factory_make("dspmp3sink", "audio-output");
#else
audioqueue2 = gst_element_factory_make("queue", "audio-queue2");
audiodec = gst_element_factory_make("mad", "audio-decoder");
audioconv =
gst_element_factory_make("audioconvert", "audio-converter");
audiosink = gst_element_factory_make("alsasink", "audio-output");
#endif
if (!(pipeline && source && parser && videodec && videosink) ||
!(videoqueue1 && videoqueue2 && audioqueue1 && audiosink)) {
/*
* FIXME: hanlde the error correctly
*/
/*
* video_alignment is not being created (below) and is causing
* problems to the ui
*/
tvplayer->gst_pipeline = NULL;
tvplayer->gst_videodec = NULL;
tvplayer->gst_videosink = NULL;
tvplayer->gst_videocolortrs = NULL;
g_warning("GstElement creation error!\n");
return FALSE;
}
#ifndef MAEMO_PLATFORM
if (!(audiodec && audioconv)) {
g_warning("GstElement for audio stream creation error!");
return FALSE;
}
#endif
tvplayer->gst_pipeline = pipeline;
tvplayer->gst_source = source;
tvplayer->gst_videodec = videodec;
tvplayer->gst_videosink = videosink;
tvplayer->gst_videocolortrs = videocolortrs;
g_object_ref(tvplayer->gst_pipeline);
g_object_ref(tvplayer->gst_source);
g_object_ref(tvplayer->gst_videodec);
g_object_ref(tvplayer->gst_videosink);
g_object_ref(tvplayer->gst_videocolortrs);
tvplayer->videoqueue1 = videoqueue1;
tvplayer->videoqueue2 = videoqueue2;
tvplayer->audioqueue1 = audioqueue1;
g_object_ref(tvplayer->videoqueue1);
g_object_ref(tvplayer->videoqueue2);
g_object_ref(tvplayer->audioqueue1);
#ifndef MAEMO_PLATFORM
tvplayer->audioqueue2 = audioqueue2;
g_object_ref(tvplayer->audioqueue2);
#endif
// g_object_set (G_OBJECT (videosink), "sync", TRUE, NULL);
g_object_set(G_OBJECT(audiosink), "sync", FALSE, NULL);
gst_bus_add_watch(gst_pipeline_get_bus
(GST_PIPELINE(tvplayer->gst_pipeline)), bus_call,
tvplayer);
gst_bin_add_many(GST_BIN(pipeline), source, parser, videoqueue1,
videodec, videoqueue2, videocolortrs, videosink,
NULL);
#ifndef MAEMO_PLATFORM
gst_bin_add_many(GST_BIN(pipeline), audioqueue1, audiodec, audioconv,
audioqueue2, audiosink, NULL);
#else
gst_bin_add_many(GST_BIN(pipeline), audioqueue1, audiosink, NULL);
#endif
{
// GstCaps *rtpcaps = gst_caps_new_simple ("application/x-rtp",
// NULL);
// gst_element_link_filtered(source, parser, rtpcaps);
}
gst_element_link(source, parser);
gst_element_link_many(videoqueue1, videodec, videoqueue2,
videocolortrs, videosink, NULL);
#ifndef MAEMO_PLATFORM
gst_element_link_many(videosink, audioqueue1, audiodec, audioconv,
audioqueue2, audiosink, NULL);
#else
gst_element_link_many(videosink, audioqueue1, audiosink, NULL);
#endif
g_signal_connect(parser, "pad-added", G_CALLBACK(new_pad_cb),
tvplayer);
return TRUE;
}
/** Configures the backend and the tv player
* for playing the recorded content A/V.
*
* FIXME: Change filename to program info or other structure about the recorded
*
* @param tvplayer the object instance.
* @param filename the file uri of the recorded content to be played.
* @return TRUE if successfull, FALSE if any error happens.
*/
gboolean
mmyth_tvplayer_record_setup(MMythTVPlayer * tvplayer,
const gchar * filename)
{
// FIXME: we should receive the uri instead of filename
const gchar *hostname =
gmyth_backend_info_get_hostname(tvplayer->backend_info);
const gint port =
gmyth_backend_info_get_port(tvplayer->backend_info);
GString *fullpath = g_string_new("myth://");
g_string_append_printf(fullpath, "%s:%d/%s", hostname, port, filename);
tvplayer->is_livetv = FALSE;
g_debug("[%s] Setting record uri to gstreamer pipeline to %s",
__FUNCTION__, fullpath->str);
g_object_set(G_OBJECT(tvplayer->gst_source), "location",
fullpath->str, NULL);
return TRUE;
}
/** Configures the backend and the tv player
* for playing the live tv.
*
* @param tvplayer the object instance.
* @return TRUE if successfull, FALSE if any error happens.
*/
gboolean
mmyth_tvplayer_livetv_setup(MMythTVPlayer * tvplayer)
{
gboolean res = TRUE;
tvplayer->livetv = gmyth_livetv_new();
if (!gmyth_livetv_setup(tvplayer->livetv, tvplayer->backend_info))
goto error;
return res;
error:
res = FALSE;
if (tvplayer->livetv != NULL) {
g_object_unref(tvplayer->livetv);
}
if (tvplayer->local_hostname != NULL) {
g_string_free(tvplayer->local_hostname, TRUE);
}
if (tvplayer->recorder != NULL) {
g_object_unref(tvplayer->recorder);
tvplayer->recorder = NULL;
}
if (tvplayer->tvchain != NULL) {
g_object_unref(tvplayer->tvchain);
tvplayer->tvchain = NULL;
}
if (tvplayer->proginfo != NULL) {
g_object_unref(tvplayer->proginfo);
tvplayer->proginfo = NULL;
}
return res;
}
/** Sets the GTK video widget for the tv player.
*
* @param tvplayer the object instance.
* @param videow the GTK video window.
* @return TRUE if successfull, FALSE if any error happens.
*/
gboolean
mmyth_tvplayer_set_widget(MMythTVPlayer * tvplayer, GtkWidget * videow)
{
tvplayer->videow = videow;
g_object_ref(videow);
g_debug("[%s] Setting widget for tv player render", __FUNCTION__);
tvplayer->expose_handler =
g_signal_connect(tvplayer->videow, "expose-event",
G_CALLBACK(expose_cb), tvplayer);
// g_signal_connect(miptv_ui->videow, "size_request",
// G_CALLBACK(cb_preferred_video_size), miptv_ui);
return TRUE;
}
static gboolean
bus_call(GstBus * bus, GstMessage * msg, gpointer data)
{
// MMythTVPlayer *tvplayer = MMYTH_TVPLAYER ( data );
// GMainLoop *loop = tvplayer->loop;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
printf("End of stream\n");
// g_idle_add ((GSourceFunc) idle_eos, data);
gst_element_set_state(GST_ELEMENT(GST_MESSAGE_SRC(msg)),
GST_STATE_NULL);
gst_element_set_locked_state(GST_ELEMENT(GST_MESSAGE_SRC(msg)),
TRUE);
break;
case GST_MESSAGE_ERROR:
{
gchar *debug;
GError *err;
gst_message_parse_error(msg, &err, &debug);
g_free(debug);
printf("Error: %s\n", err->message);
g_error_free(err);
// g_main_loop_quit (loop);
}
break;
default:
printf(gst_message_type_get_name(GST_MESSAGE_TYPE(msg)));
printf("\n");
break;
}
return TRUE;
}
#if 0
static gboolean
idle_state(gpointer data)
{
GstPlayerWindowStateChange *st = data;
if (st->old_state == GST_STATE_PLAYING) {
if (st->miptv_ui->idle_id != 0) {
g_source_remove(st->miptv_ui->idle_id);
st->miptv_ui->idle_id = 0;
}
} else if (st->new_state == GST_STATE_PLAYING) {
if (st->miptv_ui->idle_id != 0)
g_source_remove(st->miptv_ui->idle_id);
st->miptv_ui->idle_id = g_idle_add(cb_iterate, st->miptv_ui);
}
/*
* new movie loaded?
*/
if (st->old_state == GST_STATE_READY
&& st->new_state > GST_STATE_READY) {
/*
* gboolean have_video = FALSE;
*/
gtk_widget_show(st->miptv_ui->videow);
gtk_window_resize(GTK_WINDOW(st->miptv_ui->main_window), 1, 1);
}
/*
* discarded movie?
*/
if (st->old_state > GST_STATE_READY
&& st->new_state == GST_STATE_READY) {
if (st->miptv_ui->tagcache) {
gst_tag_list_free(st->miptv_ui->tagcache);
st->miptv_ui->tagcache = NULL;
}
}
gst_object_unref(GST_OBJECT(st->play));
// g_object_unref (G_OBJECT (st->win));
g_free(st);
/*
* once
*/
return FALSE;
}
#endif
/** Stops playing the current A/V.
*
* FIXME: How to proceed differently between livetv
* and recorded content?
*
* @param tvplayer the object instance.
* @return void
*/
void
mmyth_tvplayer_stop_playing(MMythTVPlayer * tvplayer)
{
g_debug("[%s] Setting gstreamer pipeline state to NULL", __FUNCTION__);
gst_element_set_state(tvplayer->gst_pipeline, GST_STATE_NULL);
if (tvplayer->is_livetv) {
if (!gmyth_recorder_stop_livetv(tvplayer->recorder)) {
g_warning("[%s] Error while stoping remote encoder",
__FUNCTION__);
}
}
}
/** Queries if the tvplayer is active playing A/V content.
*
* @param tvplayer the object instance.
* @return TRUE if the tvplayer is active, FALSE otherwise.
*/
gboolean
mmyth_tvplayer_is_playing(MMythTVPlayer * tvplayer)
{
return (GST_STATE(tvplayer->gst_pipeline) == GST_STATE_PLAYING);
}
/** Static function that sets the tvplayer state to PLAYING.
*
* @param tvplayer the object instance.
* @return TRUE if the tvplayer is active, FALSE otherwise.
*/
static gboolean
idle_play(gpointer data)
{
MMythTVPlayer *tvplayer = MMYTH_TVPLAYER(data);
g_debug("MMythTVPlayer: Setting pipeline state to PLAYING\n");
gst_element_set_state(tvplayer->gst_pipeline, GST_STATE_PLAYING);
return FALSE;
}
/** Start playing A/V with the tvplayer attributes.
*
* @param tvplayer the object instance.
*/
void
mmyth_tvplayer_start_playing(MMythTVPlayer * tvplayer)
{
// FIXME: Move this to livetv_setup??
if (tvplayer->is_livetv) {
#if 0
if (!tvplayer || !(tvplayer->proginfo)
|| !(tvplayer->local_hostname)
|| !(tvplayer->gst_source)) {
g_warning("GMythtvPlayer not ready to start playing\n");
}
if (!(tvplayer->proginfo->pathname)) {
g_warning
("[%s] Playback url is null, could not play the myth content",
__FUNCTION__);
return;
}
g_debug("MMythTVPlayer: Start playing %s",
tvplayer->proginfo->pathname->str);
#endif
g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-live",
TRUE, NULL);
#if 0
if (tvplayer->tvchain != NULL) {
GString *str_chainid =
gmyth_tvchain_get_id(tvplayer->tvchain);
g_print("[%s]\tCHAIN ID: %s\n", __FUNCTION__,
str_chainid->str);
g_object_set(G_OBJECT(tvplayer->gst_source),
"mythtv-live-chainid", g_strdup(str_chainid->str),
NULL);
if (str_chainid != NULL)
g_string_free(str_chainid, FALSE);
}
if (tvplayer->recorder != NULL)
g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-live-id",
tvplayer->recorder->recorder_num, NULL);
g_debug("[%s] Setting location to %s", __FUNCTION__,
tvplayer->proginfo->pathname->str);
/*
* Sets the gstreamer properties acording to the service access
* address
*/
g_object_set(G_OBJECT(tvplayer->gst_source), "location",
tvplayer->proginfo->pathname->str, NULL);
#endif
}
g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-version",
MYTHTV_VERSION_DEFAULT, NULL);
g_object_set(G_OBJECT(tvplayer->gst_source), "mythtv-debug",
TRUE, NULL);
g_idle_add(idle_play, tvplayer);
}