2 * empathy-streamed-media-window.c - Source for EmpathyStreamedMediaWindow
3 * Copyright (C) 2008-2009 Collabora Ltd.
4 * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27 #include <gdk/gdkkeysyms.h>
30 #include <glib/gi18n.h>
32 #include <telepathy-glib/util.h>
33 #include <telepathy-farsight/channel.h>
34 #include <telepathy-glib/util.h>
36 #include <gst/farsight/fs-element-added-notifier.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy-gtk/empathy-avatar-image.h>
41 #include <libempathy-gtk/empathy-ui-utils.h>
42 #include <libempathy-gtk/empathy-sound-manager.h>
43 #include <libempathy-gtk/empathy-geometry.h>
44 #include <libempathy-gtk/empathy-images.h>
46 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
47 #include <libempathy/empathy-debug.h>
49 #include "empathy-streamed-media-window.h"
50 #include "empathy-streamed-media-window-fullscreen.h"
51 #include "empathy-video-widget.h"
52 #include "empathy-audio-src.h"
53 #include "empathy-audio-sink.h"
54 #include "empathy-video-src.h"
55 #include "ev-sidebar.h"
57 #define BUTTON_ID "empathy-call-dtmf-button-id"
59 #define CONTENT_HBOX_BORDER_WIDTH 6
60 #define CONTENT_HBOX_SPACING 3
61 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
63 #define SELF_VIDEO_SECTION_WIDTH 160
64 #define SELF_VIDEO_SECTION_HEIGTH 120
66 /* The avatar's default width and height are set to the same value because we
67 want a square icon. */
68 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
69 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT \
70 EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
72 /* If an video input error occurs, the error message will start with "v4l" */
73 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
75 /* The time interval in milliseconds between 2 outgoing rings */
76 #define MS_BETWEEN_RING 500
78 G_DEFINE_TYPE(EmpathyStreamedMediaWindow, empathy_streamed_media_window, GTK_TYPE_WINDOW)
87 static guint signals[LAST_SIGNAL] = {0};
91 PROP_STREAMED_MEDIA_HANDLER = 1,
102 CAMERA_STATE_OFF = 0,
103 CAMERA_STATE_PREVIEW,
107 /* private structure */
108 typedef struct _EmpathyStreamedMediaWindowPriv EmpathyStreamedMediaWindowPriv;
110 struct _EmpathyStreamedMediaWindowPriv
112 gboolean dispose_has_run;
113 EmpathyStreamedMediaHandler *handler;
114 EmpathyContact *contact;
119 GtkUIManager *ui_manager;
120 GtkWidget *errors_vbox;
121 /* widget displays the video received from the remote user. This widget is
122 * alive only during call. */
123 GtkWidget *video_output;
124 GtkWidget *video_preview;
125 GtkWidget *remote_user_avatar_widget;
126 GtkWidget *self_user_avatar_widget;
128 GtkWidget *sidebar_button;
129 GtkWidget *statusbar;
130 GtkWidget *volume_button;
131 GtkWidget *redial_button;
132 GtkWidget *mic_button;
136 GtkAction *menu_fullscreen;
137 GtkAction *action_camera_on;
138 GtkWidget *tool_button_camera_off;
139 GtkWidget *tool_button_camera_preview;
140 GtkWidget *tool_button_camera_on;
142 /* The frames and boxes that contain self and remote avatar and video
143 input/output. When we redial, we destroy and re-create the boxes */
144 GtkWidget *remote_user_output_frame;
145 GtkWidget *self_user_output_frame;
146 GtkWidget *remote_user_output_hbox;
147 GtkWidget *self_user_output_hbox;
149 /* We keep a reference on the hbox which contains the main content so we can
150 easilly repack everything when toggling fullscreen */
151 GtkWidget *content_hbox;
153 /* This vbox is contained in the content_hbox and it contains the
154 self_user_output_frame and the sidebar button. When toggling fullscreen,
155 it needs to be repacked. We keep a reference on it for easier access. */
158 gulong video_output_motion_handler_id;
159 guint bus_message_source_id;
162 GtkWidget *volume_scale;
163 GtkWidget *volume_progress_bar;
164 GtkAdjustment *audio_input_adj;
166 GtkWidget *dtmf_panel;
169 GtkWidget *details_vbox;
170 GtkWidget *vcodec_encoding_label;
171 GtkWidget *acodec_encoding_label;
172 GtkWidget *vcodec_decoding_label;
173 GtkWidget *acodec_decoding_label;
175 GtkWidget *audio_remote_candidate_label;
176 GtkWidget *audio_local_candidate_label;
177 GtkWidget *video_remote_candidate_label;
178 GtkWidget *video_local_candidate_label;
179 GtkWidget *video_remote_candidate_info_img;
180 GtkWidget *video_local_candidate_info_img;
181 GtkWidget *audio_remote_candidate_info_img;
182 GtkWidget *audio_local_candidate_info_img;
184 GstElement *video_input;
185 GstElement *audio_input;
186 GstElement *audio_output;
187 GstElement *pipeline;
188 GstElement *video_tee;
191 GstElement *liveadder;
193 FsElementAddedNotifier *fsnotifier;
200 GtkWidget *video_contrast;
201 GtkWidget *video_brightness;
202 GtkWidget *video_gamma;
205 gboolean call_started;
206 gboolean sending_video;
207 CameraState camera_state;
209 EmpathyStreamedMediaWindowFullscreen *fullscreen;
210 gboolean is_fullscreen;
212 /* Those fields represent the state of the window before it actually was in
214 gboolean sidebar_was_visible_before_fs;
215 gint original_width_before_fs;
216 gint original_height_before_fs;
218 /* TRUE if the call should be started when the pipeline is playing */
219 gboolean start_call_when_playing;
220 /* TRUE if we requested to set the pipeline in the playing state */
221 gboolean pipeline_playing;
223 EmpathySoundManager *sound_mgr;
226 #define GET_PRIV(o) \
227 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, \
228 EmpathyStreamedMediaWindowPriv))
230 static void empathy_streamed_media_window_realized_cb (GtkWidget *widget,
231 EmpathyStreamedMediaWindow *window);
233 static gboolean empathy_streamed_media_window_delete_cb (GtkWidget *widget,
234 GdkEvent *event, EmpathyStreamedMediaWindow *window);
236 static gboolean empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
237 GdkEventWindowState *event, EmpathyStreamedMediaWindow *window);
239 static void empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
240 EmpathyStreamedMediaWindow *window);
242 static void empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
245 static void empathy_streamed_media_window_mic_toggled_cb (
246 GtkToggleToolButton *toggle, EmpathyStreamedMediaWindow *window);
248 static void empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
249 EmpathyStreamedMediaWindow *window);
251 static void empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
252 EmpathyStreamedMediaWindow *window);
254 static void empathy_streamed_media_window_hangup_cb (gpointer object,
255 EmpathyStreamedMediaWindow *window);
257 static void empathy_streamed_media_window_fullscreen_cb (gpointer object,
258 EmpathyStreamedMediaWindow *window);
260 static void empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window);
262 static gboolean empathy_streamed_media_window_video_button_press_cb (
263 GtkWidget *video_output, GdkEventButton *event, EmpathyStreamedMediaWindow *window);
265 static gboolean empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
266 GdkEventKey *event, EmpathyStreamedMediaWindow *window);
268 static gboolean empathy_streamed_media_window_video_output_motion_notify (
269 GtkWidget *widget, GdkEventMotion *event, EmpathyStreamedMediaWindow *window);
271 static void empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
274 static void empathy_streamed_media_window_redial_cb (gpointer object,
275 EmpathyStreamedMediaWindow *window);
277 static void empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window);
279 static void empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
282 static void empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
283 EmpathyStreamedMediaWindow *window);
285 static gboolean empathy_streamed_media_window_bus_message (GstBus *bus,
286 GstMessage *message, gpointer user_data);
289 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
290 gdouble value, EmpathyStreamedMediaWindow *window);
292 static void block_camera_control_signals (EmpathyStreamedMediaWindow *self);
293 static void unblock_camera_control_signals (EmpathyStreamedMediaWindow *self);
296 empathy_streamed_media_window_setup_toolbar (EmpathyStreamedMediaWindow *self)
298 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
299 GtkToolItem *tool_item;
300 GtkWidget *camera_off_icon;
301 GdkPixbuf *pixbuf, *modded_pixbuf;
303 /* set the icon of the 'camera off' button by greying off the webcam icon */
304 pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
305 GTK_ICON_SIZE_SMALL_TOOLBAR);
307 modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
308 gdk_pixbuf_get_width (pixbuf),
309 gdk_pixbuf_get_height (pixbuf));
311 gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
312 g_object_unref (pixbuf);
314 camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
315 g_object_unref (modded_pixbuf);
316 gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
317 priv->tool_button_camera_off), camera_off_icon);
319 /* Add an empty expanded GtkToolItem so the volume button is at the end of
321 tool_item = gtk_tool_item_new ();
322 gtk_tool_item_set_expand (tool_item, TRUE);
323 gtk_widget_show (GTK_WIDGET (tool_item));
324 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
326 priv->volume_button = gtk_volume_button_new ();
327 /* FIXME listen to the audiosinks signals and update the button according to
328 * that, for now starting out at 1.0 and assuming only the app changes the
330 gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
331 g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
332 G_CALLBACK (empathy_streamed_media_window_volume_changed_cb), self);
334 tool_item = gtk_tool_item_new ();
335 gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
336 gtk_widget_show_all (GTK_WIDGET (tool_item));
337 gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
341 dtmf_button_pressed_cb (GtkButton *button, EmpathyStreamedMediaWindow *window)
343 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
344 EmpathyTpStreamedMedia *call;
348 g_object_get (priv->handler, "tp-call", &call, NULL);
350 button_quark = g_quark_from_static_string (BUTTON_ID);
351 event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
354 empathy_tp_streamed_media_start_tone (call, event);
356 g_object_unref (call);
360 dtmf_button_released_cb (GtkButton *button, EmpathyStreamedMediaWindow *window)
362 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
363 EmpathyTpStreamedMedia *call;
365 g_object_get (priv->handler, "tp-call", &call, NULL);
367 empathy_tp_streamed_media_stop_tone (call);
369 g_object_unref (call);
373 empathy_streamed_media_window_create_dtmf (EmpathyStreamedMediaWindow *self)
381 } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
382 { "2", TP_DTMF_EVENT_DIGIT_2 },
383 { "3", TP_DTMF_EVENT_DIGIT_3 },
384 { "4", TP_DTMF_EVENT_DIGIT_4 },
385 { "5", TP_DTMF_EVENT_DIGIT_5 },
386 { "6", TP_DTMF_EVENT_DIGIT_6 },
387 { "7", TP_DTMF_EVENT_DIGIT_7 },
388 { "8", TP_DTMF_EVENT_DIGIT_8 },
389 { "9", TP_DTMF_EVENT_DIGIT_9 },
390 { "#", TP_DTMF_EVENT_HASH },
391 { "0", TP_DTMF_EVENT_DIGIT_0 },
392 { "*", TP_DTMF_EVENT_ASTERISK },
395 button_quark = g_quark_from_static_string (BUTTON_ID);
397 table = gtk_table_new (4, 3, TRUE);
399 for (i = 0; dtmfbuttons[i].label != NULL; i++)
401 GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
402 gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
403 i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
405 g_object_set_qdata (G_OBJECT (button), button_quark,
406 GUINT_TO_POINTER (dtmfbuttons[i].event));
408 g_signal_connect (G_OBJECT (button), "pressed",
409 G_CALLBACK (dtmf_button_pressed_cb), self);
410 g_signal_connect (G_OBJECT (button), "released",
411 G_CALLBACK (dtmf_button_released_cb), self);
418 empathy_streamed_media_window_create_video_input_add_slider (EmpathyStreamedMediaWindow *self,
419 gchar *label_text, GtkWidget *bin)
421 GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
422 GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
423 GtkWidget *label = gtk_label_new (label_text);
425 gtk_widget_set_sensitive (scale, FALSE);
427 gtk_container_add (GTK_CONTAINER (bin), vbox);
429 gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
430 gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
431 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
437 empathy_streamed_media_window_video_contrast_changed_cb (GtkAdjustment *adj,
438 EmpathyStreamedMediaWindow *self)
441 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
443 empathy_video_src_set_channel (priv->video_input,
444 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
448 empathy_streamed_media_window_video_brightness_changed_cb (GtkAdjustment *adj,
449 EmpathyStreamedMediaWindow *self)
452 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
454 empathy_video_src_set_channel (priv->video_input,
455 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
459 empathy_streamed_media_window_video_gamma_changed_cb (GtkAdjustment *adj,
460 EmpathyStreamedMediaWindow *self)
463 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
465 empathy_video_src_set_channel (priv->video_input,
466 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
471 empathy_streamed_media_window_create_video_input (EmpathyStreamedMediaWindow *self)
473 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
476 hbox = gtk_hbox_new (TRUE, 3);
478 priv->video_contrast = empathy_streamed_media_window_create_video_input_add_slider (
479 self, _("Contrast"), hbox);
481 priv->video_brightness = empathy_streamed_media_window_create_video_input_add_slider (
482 self, _("Brightness"), hbox);
484 priv->video_gamma = empathy_streamed_media_window_create_video_input_add_slider (
485 self, _("Gamma"), hbox);
491 empathy_streamed_media_window_setup_video_input (EmpathyStreamedMediaWindow *self)
493 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
497 supported = empathy_video_src_get_supported_channels (priv->video_input);
499 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
501 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
503 gtk_adjustment_set_value (adj,
504 empathy_video_src_get_channel (priv->video_input,
505 EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
507 g_signal_connect (G_OBJECT (adj), "value-changed",
508 G_CALLBACK (empathy_streamed_media_window_video_contrast_changed_cb), self);
510 gtk_widget_set_sensitive (priv->video_contrast, TRUE);
513 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
515 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
517 gtk_adjustment_set_value (adj,
518 empathy_video_src_get_channel (priv->video_input,
519 EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
521 g_signal_connect (G_OBJECT (adj), "value-changed",
522 G_CALLBACK (empathy_streamed_media_window_video_brightness_changed_cb), self);
523 gtk_widget_set_sensitive (priv->video_brightness, TRUE);
526 if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
528 adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
530 gtk_adjustment_set_value (adj,
531 empathy_video_src_get_channel (priv->video_input,
532 EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
534 g_signal_connect (G_OBJECT (adj), "value-changed",
535 G_CALLBACK (empathy_streamed_media_window_video_gamma_changed_cb), self);
536 gtk_widget_set_sensitive (priv->video_gamma, TRUE);
541 empathy_streamed_media_window_mic_volume_changed_cb (GtkAdjustment *adj,
542 EmpathyStreamedMediaWindow *self)
544 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
547 volume = gtk_adjustment_get_value (adj)/100.0;
549 /* Don't store the volume because of muting */
550 if (volume > 0 || gtk_toggle_tool_button_get_active (
551 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
552 priv->volume = volume;
554 /* Ensure that the toggle button is active if the volume is > 0 and inactive
555 * if it's smaller than 0 */
556 if ((volume > 0) != gtk_toggle_tool_button_get_active (
557 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
558 gtk_toggle_tool_button_set_active (
559 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
561 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
566 empathy_streamed_media_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
567 gdouble level, EmpathyStreamedMediaWindow *window)
570 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
572 value = CLAMP (pow (10, level / 20), 0.0, 1.0);
573 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
578 empathy_streamed_media_window_create_audio_input (EmpathyStreamedMediaWindow *self)
580 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
581 GtkWidget *hbox, *vbox, *label;
583 hbox = gtk_hbox_new (TRUE, 3);
585 vbox = gtk_vbox_new (FALSE, 3);
586 gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
588 priv->volume_scale = gtk_vscale_new_with_range (0, 150, 100);
589 gtk_range_set_inverted (GTK_RANGE (priv->volume_scale), TRUE);
590 label = gtk_label_new (_("Volume"));
592 priv->audio_input_adj = gtk_range_get_adjustment (
593 GTK_RANGE (priv->volume_scale));
594 priv->volume = empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
595 (priv->audio_input));
596 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
598 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
599 G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), self);
601 gtk_box_pack_start (GTK_BOX (vbox), priv->volume_scale, TRUE, TRUE, 3);
602 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
604 priv->volume_progress_bar = gtk_progress_bar_new ();
606 gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->volume_progress_bar),
607 GTK_ORIENTATION_VERTICAL);
609 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
612 gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
619 create_video_output_widget (EmpathyStreamedMediaWindow *self)
621 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
624 g_assert (priv->video_output == NULL);
625 g_assert (priv->pipeline != NULL);
627 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
628 priv->video_output = empathy_video_widget_new (bus);
630 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
631 priv->video_output, TRUE, TRUE, 0);
633 gtk_widget_add_events (priv->video_output,
634 GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
635 g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
636 G_CALLBACK (empathy_streamed_media_window_video_button_press_cb), self);
638 g_object_unref (bus);
642 create_audio_output (EmpathyStreamedMediaWindow *self)
644 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
646 g_assert (priv->audio_output == NULL);
647 priv->audio_output = empathy_audio_sink_new ();
648 gst_object_ref (priv->audio_output);
649 gst_object_sink (priv->audio_output);
653 create_video_input (EmpathyStreamedMediaWindow *self)
655 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
657 g_assert (priv->video_input == NULL);
658 priv->video_input = empathy_video_src_new ();
659 gst_object_ref (priv->video_input);
660 gst_object_sink (priv->video_input);
664 create_audio_input (EmpathyStreamedMediaWindow *self)
666 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
668 g_assert (priv->audio_input == NULL);
669 priv->audio_input = empathy_audio_src_new ();
670 gst_object_ref (priv->audio_input);
671 gst_object_sink (priv->audio_input);
673 tp_g_signal_connect_object (priv->audio_input, "peak-level-changed",
674 G_CALLBACK (empathy_streamed_media_window_audio_input_level_changed_cb),
679 add_video_preview_to_pipeline (EmpathyStreamedMediaWindow *self)
681 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
684 g_assert (priv->video_preview != NULL);
685 g_assert (priv->pipeline != NULL);
686 g_assert (priv->video_input != NULL);
687 g_assert (priv->video_tee != NULL);
689 preview = empathy_video_widget_get_element (
690 EMPATHY_VIDEO_WIDGET (priv->video_preview));
692 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_input))
694 g_warning ("Could not add video input to pipeline");
698 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->video_tee))
700 g_warning ("Could not add video tee to pipeline");
704 if (!gst_bin_add (GST_BIN (priv->pipeline), preview))
706 g_warning ("Could not add video preview to pipeline");
710 if (!gst_element_link (priv->video_input, priv->video_tee))
712 g_warning ("Could not link video input to video tee");
716 if (!gst_element_link (priv->video_tee, preview))
718 g_warning ("Could not link video tee to video preview");
724 create_video_preview (EmpathyStreamedMediaWindow *self)
726 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
729 g_assert (priv->video_preview == NULL);
730 g_assert (priv->video_tee == NULL);
732 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
734 priv->video_preview = empathy_video_widget_new_with_size (bus,
735 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
736 g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
738 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
739 priv->video_preview, TRUE, TRUE, 0);
741 priv->video_tee = gst_element_factory_make ("tee", NULL);
742 gst_object_ref (priv->video_tee);
743 gst_object_sink (priv->video_tee);
745 g_object_unref (bus);
749 play_camera (EmpathyStreamedMediaWindow *window,
752 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
756 if (priv->video_preview == NULL)
758 create_video_preview (window);
759 add_video_preview_to_pipeline (window);
763 state = GST_STATE_PLAYING;
765 state = GST_STATE_NULL;
767 preview = empathy_video_widget_get_element (
768 EMPATHY_VIDEO_WIDGET (priv->video_preview));
770 gst_element_set_state (preview, state);
771 gst_element_set_state (priv->video_input, state);
772 gst_element_set_state (priv->video_tee, state);
776 display_video_preview (EmpathyStreamedMediaWindow *self,
779 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
783 /* Display the preview and hide the self avatar */
784 DEBUG ("Show video preview");
786 play_camera (self, TRUE);
787 gtk_widget_show (priv->video_preview);
788 gtk_widget_hide (priv->self_user_avatar_widget);
792 /* Display the self avatar and hide the preview */
793 DEBUG ("Show self avatar");
795 if (priv->video_preview != NULL)
797 gtk_widget_hide (priv->video_preview);
798 play_camera (self, FALSE);
800 gtk_widget_show (priv->self_user_avatar_widget);
805 empathy_streamed_media_window_set_state_connecting (EmpathyStreamedMediaWindow *window)
807 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
809 empathy_streamed_media_window_status_message (window, _("Connecting…"));
810 priv->call_state = CONNECTING;
813 empathy_sound_manager_start_playing (priv->sound_mgr, GTK_WIDGET (window),
814 EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
818 disable_camera (EmpathyStreamedMediaWindow *self)
820 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
822 if (priv->camera_state == CAMERA_STATE_OFF)
825 DEBUG ("Disable camera");
827 display_video_preview (self, FALSE);
829 if (priv->camera_state == CAMERA_STATE_ON)
830 empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
832 block_camera_control_signals (self);
833 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
834 priv->tool_button_camera_on), FALSE);
835 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
836 priv->tool_button_camera_preview), FALSE);
838 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
839 priv->tool_button_camera_off), TRUE);
840 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
842 unblock_camera_control_signals (self);
844 priv->camera_state = CAMERA_STATE_OFF;
848 tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
849 EmpathyStreamedMediaWindow *self)
851 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
853 if (!gtk_toggle_tool_button_get_active (toggle))
855 if (priv->camera_state == CAMERA_STATE_OFF)
857 /* We can't change the state by disabling the button */
858 block_camera_control_signals (self);
859 gtk_toggle_tool_button_set_active (toggle, TRUE);
860 unblock_camera_control_signals (self);
866 disable_camera (self);
870 enable_preview (EmpathyStreamedMediaWindow *self)
872 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
874 if (priv->camera_state == CAMERA_STATE_PREVIEW)
877 DEBUG ("Enable preview");
879 if (priv->camera_state == CAMERA_STATE_ON)
881 /* preview is already displayed so we just have to stop sending */
882 empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_PREVIEW);
886 display_video_preview (self, TRUE);
889 block_camera_control_signals (self);
890 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
891 priv->tool_button_camera_off), FALSE);
892 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
893 priv->tool_button_camera_on), FALSE);
895 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
896 priv->tool_button_camera_preview), TRUE);
897 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
898 CAMERA_STATE_PREVIEW);
899 unblock_camera_control_signals (self);
901 priv->camera_state = CAMERA_STATE_PREVIEW;
905 tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
906 EmpathyStreamedMediaWindow *self)
908 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
910 if (!gtk_toggle_tool_button_get_active (toggle))
912 if (priv->camera_state == CAMERA_STATE_PREVIEW)
914 /* We can't change the state by disabling the button */
915 block_camera_control_signals (self);
916 gtk_toggle_tool_button_set_active (toggle, TRUE);
917 unblock_camera_control_signals (self);
923 enable_preview (self);
927 enable_camera (EmpathyStreamedMediaWindow *self)
929 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
931 if (priv->camera_state == CAMERA_STATE_ON)
934 if (priv->video_input == NULL)
936 DEBUG ("Can't enable camera, no input");
941 DEBUG ("Enable camera");
943 empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_ON);
945 block_camera_control_signals (self);
946 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
947 priv->tool_button_camera_off), FALSE);
948 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
949 priv->tool_button_camera_preview), FALSE);
951 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
952 priv->tool_button_camera_on), TRUE);
953 gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera_on),
955 unblock_camera_control_signals (self);
957 priv->camera_state = CAMERA_STATE_ON;
961 tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
962 EmpathyStreamedMediaWindow *self)
964 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
966 if (!gtk_toggle_tool_button_get_active (toggle))
968 if (priv->camera_state == CAMERA_STATE_ON)
970 /* We can't change the state by disabling the button */
971 block_camera_control_signals (self);
972 gtk_toggle_tool_button_set_active (toggle, TRUE);
973 unblock_camera_control_signals (self);
979 enable_camera (self);
983 action_camera_change_cb (GtkRadioAction *action,
984 GtkRadioAction *current,
985 EmpathyStreamedMediaWindow *self)
989 state = gtk_radio_action_get_current_value (current);
993 case CAMERA_STATE_OFF:
994 disable_camera (self);
997 case CAMERA_STATE_PREVIEW:
998 enable_preview (self);
1001 case CAMERA_STATE_ON:
1002 enable_camera (self);
1006 g_assert_not_reached ();
1011 create_pipeline (EmpathyStreamedMediaWindow *self)
1013 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1016 g_assert (priv->pipeline == NULL);
1018 priv->pipeline = gst_pipeline_new (NULL);
1019 priv->pipeline_playing = FALSE;
1021 bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1022 priv->bus_message_source_id = gst_bus_add_watch (bus,
1023 empathy_streamed_media_window_bus_message, self);
1025 g_object_unref (bus);
1030 empathy_streamed_media_window_init (EmpathyStreamedMediaWindow *self)
1032 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1034 GtkWidget *top_vbox;
1040 GError *error = NULL;
1043 filename = empathy_file_lookup ("empathy-call-window.ui", "src");
1044 gui = empathy_builder_get_file (filename,
1045 "call_window_vbox", &top_vbox,
1046 "errors_vbox", &priv->errors_vbox,
1047 "pane", &priv->pane,
1048 "statusbar", &priv->statusbar,
1049 "redial", &priv->redial_button,
1050 "microphone", &priv->mic_button,
1051 "toolbar", &priv->toolbar,
1052 "menuredial", &priv->redial,
1053 "ui_manager", &priv->ui_manager,
1054 "menufullscreen", &priv->menu_fullscreen,
1055 "camera_off", &priv->tool_button_camera_off,
1056 "camera_preview", &priv->tool_button_camera_preview,
1057 "camera_on", &priv->tool_button_camera_on,
1058 "action_camera_on", &priv->action_camera_on,
1059 "details_vbox", &priv->details_vbox,
1060 "vcodec_encoding_label", &priv->vcodec_encoding_label,
1061 "acodec_encoding_label", &priv->acodec_encoding_label,
1062 "acodec_decoding_label", &priv->acodec_decoding_label,
1063 "vcodec_decoding_label", &priv->vcodec_decoding_label,
1064 "audio_remote_candidate_label", &priv->audio_remote_candidate_label,
1065 "audio_local_candidate_label", &priv->audio_local_candidate_label,
1066 "video_remote_candidate_label", &priv->video_remote_candidate_label,
1067 "video_local_candidate_label", &priv->video_local_candidate_label,
1068 "video_remote_candidate_info_img", &priv->video_remote_candidate_info_img,
1069 "video_local_candidate_info_img", &priv->video_local_candidate_info_img,
1070 "audio_remote_candidate_info_img", &priv->audio_remote_candidate_info_img,
1071 "audio_local_candidate_info_img", &priv->audio_local_candidate_info_img,
1075 empathy_builder_connect (gui, self,
1076 "menuhangup", "activate", empathy_streamed_media_window_hangup_cb,
1077 "hangup", "clicked", empathy_streamed_media_window_hangup_cb,
1078 "menuredial", "activate", empathy_streamed_media_window_redial_cb,
1079 "redial", "clicked", empathy_streamed_media_window_redial_cb,
1080 "microphone", "toggled", empathy_streamed_media_window_mic_toggled_cb,
1081 "menufullscreen", "activate", empathy_streamed_media_window_fullscreen_cb,
1082 "camera_off", "toggled", tool_button_camera_off_toggled_cb,
1083 "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
1084 "camera_on", "toggled", tool_button_camera_on_toggled_cb,
1085 "action_camera_on", "changed", action_camera_change_cb,
1088 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1090 priv->lock = g_mutex_new ();
1092 gtk_container_add (GTK_CONTAINER (self), top_vbox);
1094 priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
1095 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1096 CONTENT_HBOX_BORDER_WIDTH);
1097 gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
1099 /* remote user output frame */
1100 priv->remote_user_output_frame = gtk_frame_new (NULL);
1101 gtk_widget_set_size_request (priv->remote_user_output_frame,
1102 EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
1103 gtk_box_pack_start (GTK_BOX (priv->content_hbox),
1104 priv->remote_user_output_frame, TRUE, TRUE,
1105 CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1107 priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
1109 priv->remote_user_avatar_widget = gtk_image_new ();
1111 gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
1112 priv->remote_user_avatar_widget, TRUE, TRUE, 0);
1114 gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
1115 priv->remote_user_output_hbox);
1117 /* self user output frame */
1118 priv->self_user_output_frame = gtk_frame_new (NULL);
1119 gtk_widget_set_size_request (priv->self_user_output_frame,
1120 SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
1122 priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
1124 priv->self_user_avatar_widget = gtk_image_new ();
1125 gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
1126 priv->self_user_avatar_widget, TRUE, TRUE, 0);
1128 gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
1129 priv->self_user_output_hbox);
1131 create_pipeline (self);
1132 create_video_output_widget (self);
1133 create_audio_input (self);
1134 create_audio_output (self);
1135 create_video_input (self);
1137 priv->fsnotifier = fs_element_added_notifier_new ();
1138 fs_element_added_notifier_add (priv->fsnotifier, GST_BIN (priv->pipeline));
1140 /* The call will be started as soon the pipeline is playing */
1141 priv->start_call_when_playing = TRUE;
1143 keyfile = g_key_file_new ();
1144 filename = empathy_file_lookup ("element-properties", "data");
1145 if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error))
1147 fs_element_added_notifier_set_properties_from_keyfile (priv->fsnotifier,
1152 g_warning ("Could not load element-properties file: %s", error->message);
1153 g_key_file_free (keyfile);
1154 g_clear_error (&error);
1158 priv->vbox = gtk_vbox_new (FALSE, 3);
1159 gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
1160 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
1161 gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame,
1164 empathy_streamed_media_window_setup_toolbar (self);
1166 priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
1167 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1168 g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
1169 G_CALLBACK (empathy_streamed_media_window_sidebar_toggled_cb), self);
1171 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1173 h = gtk_hbox_new (FALSE, 3);
1174 gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
1175 gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
1177 priv->sidebar = ev_sidebar_new ();
1178 g_signal_connect (G_OBJECT (priv->sidebar),
1179 "hide", G_CALLBACK (empathy_streamed_media_window_sidebar_hidden_cb), self);
1180 g_signal_connect (G_OBJECT (priv->sidebar),
1181 "show", G_CALLBACK (empathy_streamed_media_window_sidebar_shown_cb), self);
1182 gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
1184 page = empathy_streamed_media_window_create_audio_input (self);
1185 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "audio-input",
1186 _("Audio input"), page);
1188 page = empathy_streamed_media_window_create_video_input (self);
1189 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "video-input",
1190 _("Video input"), page);
1192 priv->dtmf_panel = empathy_streamed_media_window_create_dtmf (self);
1193 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "dialpad",
1194 _("Dialpad"), priv->dtmf_panel);
1196 gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
1198 /* Put the details vbox in a scroll window as it can require a lot of
1199 * horizontal space. */
1200 scroll = gtk_scrolled_window_new (NULL, NULL);
1201 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll),
1202 priv->details_vbox);
1204 ev_sidebar_add_page (EV_SIDEBAR (priv->sidebar), "details",
1205 _("Details"), scroll);
1207 gtk_widget_show_all (top_vbox);
1209 gtk_widget_hide (priv->sidebar);
1211 priv->fullscreen = empathy_streamed_media_window_fullscreen_new (self);
1212 empathy_streamed_media_window_fullscreen_set_video_widget (priv->fullscreen,
1213 priv->video_output);
1214 g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
1215 "clicked", G_CALLBACK (empathy_streamed_media_window_fullscreen_cb), self);
1217 g_signal_connect (G_OBJECT (self), "realize",
1218 G_CALLBACK (empathy_streamed_media_window_realized_cb), self);
1220 g_signal_connect (G_OBJECT (self), "delete-event",
1221 G_CALLBACK (empathy_streamed_media_window_delete_cb), self);
1223 g_signal_connect (G_OBJECT (self), "window-state-event",
1224 G_CALLBACK (empathy_streamed_media_window_state_event_cb), self);
1226 g_signal_connect (G_OBJECT (self), "key-press-event",
1227 G_CALLBACK (empathy_streamed_media_window_key_press_cb), self);
1229 priv->timer = g_timer_new ();
1231 g_object_ref (priv->ui_manager);
1232 g_object_unref (gui);
1234 priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1236 empathy_geometry_bind (GTK_WINDOW (self), "call-window");
1239 /* Instead of specifying a width and a height, we specify only one size. That's
1240 because we want a square avatar icon. */
1242 init_contact_avatar_with_size (EmpathyContact *contact,
1243 GtkWidget *image_widget,
1246 GdkPixbuf *pixbuf_avatar = NULL;
1248 if (contact != NULL)
1250 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
1254 if (pixbuf_avatar == NULL)
1256 pixbuf_avatar = empathy_pixbuf_from_icon_name_sized (
1257 EMPATHY_IMAGE_AVATAR_DEFAULT, size);
1260 gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
1262 if (pixbuf_avatar != NULL)
1263 g_object_unref (pixbuf_avatar);
1267 set_window_title (EmpathyStreamedMediaWindow *self)
1269 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1272 /* translators: Call is a noun and %s is the contact name. This string
1273 * is used in the window title */
1274 tmp = g_strdup_printf (_("Call with %s"),
1275 empathy_contact_get_alias (priv->contact));
1276 gtk_window_set_title (GTK_WINDOW (self), tmp);
1281 contact_name_changed_cb (EmpathyContact *contact,
1282 GParamSpec *pspec, EmpathyStreamedMediaWindow *self)
1284 set_window_title (self);
1288 contact_avatar_changed_cb (EmpathyContact *contact,
1289 GParamSpec *pspec, GtkWidget *avatar_widget)
1292 GtkAllocation allocation;
1294 gtk_widget_get_allocation (avatar_widget, &allocation);
1295 size = allocation.height;
1299 /* the widget is not allocated yet, set a default size */
1300 size = MIN (REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT,
1301 REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH);
1304 init_contact_avatar_with_size (contact, avatar_widget, size);
1308 empathy_streamed_media_window_got_self_contact_cb (TpConnection *connection,
1309 EmpathyContact *contact, const GError *error, gpointer user_data,
1310 GObject *weak_object)
1312 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1313 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1315 init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
1316 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1318 g_signal_connect (contact, "notify::avatar",
1319 G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
1323 empathy_streamed_media_window_setup_avatars (EmpathyStreamedMediaWindow *self,
1324 EmpathyStreamedMediaHandler *handler)
1326 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1328 g_object_get (handler, "contact", &(priv->contact), NULL);
1330 if (priv->contact != NULL)
1332 TpConnection *connection;
1334 set_window_title (self);
1336 g_signal_connect (priv->contact, "notify::name",
1337 G_CALLBACK (contact_name_changed_cb), self);
1338 g_signal_connect (priv->contact, "notify::avatar",
1339 G_CALLBACK (contact_avatar_changed_cb),
1340 priv->remote_user_avatar_widget);
1342 /* Retreiving the self avatar */
1343 connection = empathy_contact_get_connection (priv->contact);
1344 empathy_tp_contact_factory_get_from_handle (connection,
1345 tp_connection_get_self_handle (connection),
1346 empathy_streamed_media_window_got_self_contact_cb, self, NULL, G_OBJECT (self));
1350 g_warning ("call handler doesn't have a contact");
1351 /* translators: Call is a noun. This string is used in the window
1353 gtk_window_set_title (GTK_WINDOW (self), _("Call"));
1355 /* Since we can't access the remote contact, we can't get a connection
1356 to it and can't get the self contact (and its avatar). This means
1357 that we have to manually set the self avatar. */
1358 init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
1359 MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
1362 init_contact_avatar_with_size (priv->contact,
1363 priv->remote_user_avatar_widget,
1364 MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
1365 REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
1367 /* The remote avatar is shown by default and will be hidden when we receive
1368 video from the remote side. */
1369 gtk_widget_hide (priv->video_output);
1370 gtk_widget_show (priv->remote_user_avatar_widget);
1374 update_send_codec (EmpathyStreamedMediaWindow *self,
1377 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1384 codec = empathy_streamed_media_handler_get_send_audio_codec (priv->handler);
1385 widget = priv->acodec_encoding_label;
1389 codec = empathy_streamed_media_handler_get_send_video_codec (priv->handler);
1390 widget = priv->vcodec_encoding_label;
1396 tmp = g_strdup_printf ("%s/%u", codec->encoding_name, codec->clock_rate);
1397 gtk_label_set_text (GTK_LABEL (widget), tmp);
1402 send_audio_codec_notify_cb (GObject *object,
1406 EmpathyStreamedMediaWindow *self = user_data;
1408 update_send_codec (self, TRUE);
1412 send_video_codec_notify_cb (GObject *object,
1416 EmpathyStreamedMediaWindow *self = user_data;
1418 update_send_codec (self, FALSE);
1422 update_recv_codec (EmpathyStreamedMediaWindow *self,
1425 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1428 GString *str = NULL;
1432 codecs = empathy_streamed_media_handler_get_recv_audio_codecs (priv->handler);
1433 widget = priv->acodec_decoding_label;
1437 codecs = empathy_streamed_media_handler_get_recv_video_codecs (priv->handler);
1438 widget = priv->vcodec_decoding_label;
1444 for (l = codecs; l != NULL; l = g_list_next (l))
1446 FsCodec *codec = l->data;
1449 str = g_string_new (NULL);
1451 g_string_append (str, ", ");
1453 g_string_append_printf (str, "%s/%u", codec->encoding_name,
1457 gtk_label_set_text (GTK_LABEL (widget), str->str);
1458 g_string_free (str, TRUE);
1462 recv_audio_codecs_notify_cb (GObject *object,
1466 EmpathyStreamedMediaWindow *self = user_data;
1468 update_recv_codec (self, TRUE);
1472 recv_video_codecs_notify_cb (GObject *object,
1476 EmpathyStreamedMediaWindow *self = user_data;
1478 update_recv_codec (self, FALSE);
1481 static const gchar *
1482 candidate_type_to_str (FsCandidate *candidate)
1484 switch (candidate->type)
1486 case FS_CANDIDATE_TYPE_HOST:
1488 case FS_CANDIDATE_TYPE_SRFLX:
1489 return "server reflexive";
1490 case FS_CANDIDATE_TYPE_PRFLX:
1491 return "peer reflexive";
1492 case FS_CANDIDATE_TYPE_RELAY:
1494 case FS_CANDIDATE_TYPE_MULTICAST:
1501 static const gchar *
1502 candidate_type_to_desc (FsCandidate *candidate)
1504 switch (candidate->type)
1506 case FS_CANDIDATE_TYPE_HOST:
1507 return _("The IP address as seen by the machine");
1508 case FS_CANDIDATE_TYPE_SRFLX:
1509 return _("The IP address as seen by a server on the Internet");
1510 case FS_CANDIDATE_TYPE_PRFLX:
1511 return _("The IP address of the peer as seen by the other side");
1512 case FS_CANDIDATE_TYPE_RELAY:
1513 return _("The IP address of a relay server");
1514 case FS_CANDIDATE_TYPE_MULTICAST:
1515 return _("The IP address of the multicast group");
1522 update_candidat_widget (EmpathyStreamedMediaWindow *self,
1525 FsCandidate *candidate)
1529 g_assert (candidate != NULL);
1530 str = g_strdup_printf ("%s %u (%s)", candidate->ip,
1531 candidate->port, candidate_type_to_str (candidate));
1533 gtk_label_set_text (GTK_LABEL (label), str);
1534 gtk_widget_set_tooltip_text (img, candidate_type_to_desc (candidate));
1540 candidates_changed_cb (GObject *object,
1542 EmpathyStreamedMediaWindow *self)
1544 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1545 FsCandidate *candidate = NULL;
1547 if (type == FS_MEDIA_TYPE_VIDEO)
1549 /* Update remote candidate */
1550 candidate = empathy_streamed_media_handler_get_video_remote_candidate (
1553 update_candidat_widget (self, priv->video_remote_candidate_label,
1554 priv->video_remote_candidate_info_img, candidate);
1556 /* Update local candidate */
1557 candidate = empathy_streamed_media_handler_get_video_local_candidate (
1560 update_candidat_widget (self, priv->video_local_candidate_label,
1561 priv->video_local_candidate_info_img, candidate);
1565 /* Update remote candidate */
1566 candidate = empathy_streamed_media_handler_get_audio_remote_candidate (
1569 update_candidat_widget (self, priv->audio_remote_candidate_label,
1570 priv->audio_remote_candidate_info_img, candidate);
1572 /* Update local candidate */
1573 candidate = empathy_streamed_media_handler_get_audio_local_candidate (
1576 update_candidat_widget (self, priv->audio_local_candidate_label,
1577 priv->audio_local_candidate_info_img, candidate);
1582 empathy_streamed_media_window_constructed (GObject *object)
1584 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1585 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1586 EmpathyTpStreamedMedia *call;
1588 g_assert (priv->handler != NULL);
1590 g_object_get (priv->handler, "tp-call", &call, NULL);
1591 priv->outgoing = (call == NULL);
1593 g_object_unref (call);
1595 empathy_streamed_media_window_setup_avatars (self, priv->handler);
1596 empathy_streamed_media_window_set_state_connecting (self);
1598 if (!empathy_streamed_media_handler_has_initial_video (priv->handler))
1600 gtk_toggle_tool_button_set_active (
1601 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
1603 /* If call has InitialVideo, the preview will be started once the call has
1604 * been started (start_call()). */
1606 update_send_codec (self, TRUE);
1607 update_send_codec (self, FALSE);
1608 update_recv_codec (self, TRUE);
1609 update_recv_codec (self, FALSE);
1611 tp_g_signal_connect_object (priv->handler, "notify::send-audio-codec",
1612 G_CALLBACK (send_audio_codec_notify_cb), self, 0);
1613 tp_g_signal_connect_object (priv->handler, "notify::send-video-codec",
1614 G_CALLBACK (send_video_codec_notify_cb), self, 0);
1615 tp_g_signal_connect_object (priv->handler, "notify::recv-audio-codecs",
1616 G_CALLBACK (recv_audio_codecs_notify_cb), self, 0);
1617 tp_g_signal_connect_object (priv->handler, "notify::recv-video-codecs",
1618 G_CALLBACK (recv_video_codecs_notify_cb), self, 0);
1620 tp_g_signal_connect_object (priv->handler, "candidates-changed",
1621 G_CALLBACK (candidates_changed_cb), self, 0);
1624 static void empathy_streamed_media_window_dispose (GObject *object);
1625 static void empathy_streamed_media_window_finalize (GObject *object);
1628 empathy_streamed_media_window_set_property (GObject *object,
1629 guint property_id, const GValue *value, GParamSpec *pspec)
1631 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1633 switch (property_id)
1635 case PROP_STREAMED_MEDIA_HANDLER:
1636 priv->handler = g_value_dup_object (value);
1639 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1644 empathy_streamed_media_window_get_property (GObject *object,
1645 guint property_id, GValue *value, GParamSpec *pspec)
1647 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (object);
1649 switch (property_id)
1651 case PROP_STREAMED_MEDIA_HANDLER:
1652 g_value_set_object (value, priv->handler);
1655 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1660 empathy_streamed_media_window_class_init (
1661 EmpathyStreamedMediaWindowClass *empathy_streamed_media_window_class)
1663 GObjectClass *object_class = G_OBJECT_CLASS (empathy_streamed_media_window_class);
1664 GParamSpec *param_spec;
1666 g_type_class_add_private (empathy_streamed_media_window_class,
1667 sizeof (EmpathyStreamedMediaWindowPriv));
1669 object_class->constructed = empathy_streamed_media_window_constructed;
1670 object_class->set_property = empathy_streamed_media_window_set_property;
1671 object_class->get_property = empathy_streamed_media_window_get_property;
1673 object_class->dispose = empathy_streamed_media_window_dispose;
1674 object_class->finalize = empathy_streamed_media_window_finalize;
1676 param_spec = g_param_spec_object ("handler",
1677 "handler", "The call handler",
1678 EMPATHY_TYPE_STREAMED_MEDIA_HANDLER,
1679 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1680 g_object_class_install_property (object_class,
1681 PROP_STREAMED_MEDIA_HANDLER, param_spec);
1685 empathy_streamed_media_window_video_stream_changed_cb (EmpathyTpStreamedMedia *call,
1686 GParamSpec *property, EmpathyStreamedMediaWindow *self)
1688 DEBUG ("video stream changed");
1689 empathy_streamed_media_window_update_avatars_visibility (call, self);
1693 empathy_streamed_media_window_dispose (GObject *object)
1695 EmpathyTpStreamedMedia *call;
1696 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1697 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1699 if (priv->dispose_has_run)
1702 priv->dispose_has_run = TRUE;
1704 g_object_get (priv->handler, "tp-call", &call, NULL);
1708 g_object_unref (call);
1711 if (priv->handler != NULL)
1713 empathy_streamed_media_handler_stop_call (priv->handler);
1714 g_object_unref (priv->handler);
1716 priv->handler = NULL;
1718 if (priv->bus_message_source_id != 0)
1720 g_source_remove (priv->bus_message_source_id);
1721 priv->bus_message_source_id = 0;
1724 if (priv->pipeline != NULL)
1725 g_object_unref (priv->pipeline);
1726 priv->pipeline = NULL;
1728 if (priv->video_input != NULL)
1729 g_object_unref (priv->video_input);
1730 priv->video_input = NULL;
1732 if (priv->audio_input != NULL)
1733 g_object_unref (priv->audio_input);
1734 priv->audio_input = NULL;
1736 if (priv->audio_output != NULL)
1737 g_object_unref (priv->audio_output);
1738 priv->audio_output = NULL;
1740 if (priv->video_tee != NULL)
1741 g_object_unref (priv->video_tee);
1742 priv->video_tee = NULL;
1744 if (priv->liveadder != NULL)
1745 gst_object_unref (priv->liveadder);
1746 priv->liveadder = NULL;
1748 if (priv->fsnotifier != NULL)
1749 g_object_unref (priv->fsnotifier);
1750 priv->fsnotifier = NULL;
1752 if (priv->timer_id != 0)
1753 g_source_remove (priv->timer_id);
1756 if (priv->ui_manager != NULL)
1757 g_object_unref (priv->ui_manager);
1758 priv->ui_manager = NULL;
1760 if (priv->fullscreen != NULL)
1761 g_object_unref (priv->fullscreen);
1762 priv->fullscreen = NULL;
1764 if (priv->contact != NULL)
1766 g_signal_handlers_disconnect_by_func (priv->contact,
1767 contact_name_changed_cb, self);
1768 g_object_unref (priv->contact);
1769 priv->contact = NULL;
1772 tp_clear_object (&priv->sound_mgr);
1774 /* release any references held by the object here */
1775 if (G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose)
1776 G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->dispose (object);
1780 disconnect_video_output_motion_handler (EmpathyStreamedMediaWindow *self)
1782 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1784 if (priv->video_output_motion_handler_id != 0)
1786 g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1787 priv->video_output_motion_handler_id);
1788 priv->video_output_motion_handler_id = 0;
1793 empathy_streamed_media_window_finalize (GObject *object)
1795 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (object);
1796 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1798 disconnect_video_output_motion_handler (self);
1800 /* free any data held directly by the object here */
1801 g_mutex_free (priv->lock);
1803 g_timer_destroy (priv->timer);
1805 G_OBJECT_CLASS (empathy_streamed_media_window_parent_class)->finalize (object);
1809 EmpathyStreamedMediaWindow *
1810 empathy_streamed_media_window_new (EmpathyStreamedMediaHandler *handler)
1812 return EMPATHY_STREAMED_MEDIA_WINDOW (
1813 g_object_new (EMPATHY_TYPE_STREAMED_MEDIA_WINDOW, "handler", handler, NULL));
1817 empathy_streamed_media_window_conference_added_cb (EmpathyStreamedMediaHandler *handler,
1818 GstElement *conference, gpointer user_data)
1820 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1821 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1823 gst_bin_add (GST_BIN (priv->pipeline), conference);
1825 gst_element_set_state (conference, GST_STATE_PLAYING);
1829 empathy_streamed_media_window_request_resource_cb (EmpathyStreamedMediaHandler *handler,
1830 FsMediaType type, FsStreamDirection direction, gpointer user_data)
1832 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
1833 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1835 if (type != FS_MEDIA_TYPE_VIDEO)
1838 if (direction == FS_DIRECTION_RECV)
1841 /* video and direction is send */
1842 return priv->video_input != NULL;
1846 empathy_streamed_media_window_reset_pipeline (EmpathyStreamedMediaWindow *self)
1848 GstStateChangeReturn state_change_return;
1849 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1851 if (priv->pipeline == NULL)
1854 if (priv->bus_message_source_id != 0)
1856 g_source_remove (priv->bus_message_source_id);
1857 priv->bus_message_source_id = 0;
1860 state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1862 if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1863 state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1865 if (priv->pipeline != NULL)
1866 g_object_unref (priv->pipeline);
1867 priv->pipeline = NULL;
1869 g_signal_handlers_disconnect_by_func (priv->audio_input_adj,
1870 empathy_streamed_media_window_mic_volume_changed_cb, self);
1872 if (priv->video_tee != NULL)
1873 g_object_unref (priv->video_tee);
1874 priv->video_tee = NULL;
1876 if (priv->video_preview != NULL)
1877 gtk_widget_destroy (priv->video_preview);
1878 priv->video_preview = NULL;
1880 priv->liveadder = NULL;
1881 priv->funnel = NULL;
1883 create_pipeline (self);
1884 /* Call will be started when user will hit the 'redial' button */
1885 priv->start_call_when_playing = FALSE;
1886 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1892 g_message ("Error: could not destroy pipeline. Closing call window");
1893 gtk_widget_destroy (GTK_WIDGET (self));
1900 reset_details_pane (EmpathyStreamedMediaWindow *self)
1902 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1904 /* translators: encoding video codec is unknown */
1905 gtk_label_set_text (GTK_LABEL (priv->vcodec_encoding_label),
1906 C_("encoding video codec", "Unknown"));
1907 /* translators: encoding audio codec is unknown */
1908 gtk_label_set_text (GTK_LABEL (priv->acodec_encoding_label),
1909 C_("encoding audio codec", "Unknown"));
1910 /* translators: decoding video codec is unknown */
1911 gtk_label_set_text (GTK_LABEL (priv->vcodec_decoding_label),
1912 C_("decoding video codec", "Unknown"));
1913 /* translators: decoding audio codec is unknown */
1914 gtk_label_set_text (GTK_LABEL (priv->acodec_decoding_label),
1915 C_("decoding audio codec", "Unknown"));
1919 empathy_streamed_media_window_disconnected (EmpathyStreamedMediaWindow *self,
1922 gboolean could_disconnect = FALSE;
1923 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
1924 gboolean could_reset_pipeline;
1926 /* Leave full screen mode if needed */
1927 gtk_window_unfullscreen (GTK_WINDOW (self));
1929 gtk_action_set_sensitive (priv->menu_fullscreen, FALSE);
1931 could_reset_pipeline = empathy_streamed_media_window_reset_pipeline (self);
1933 if (priv->call_state == CONNECTING)
1934 empathy_sound_manager_stop (priv->sound_mgr,
1935 EMPATHY_SOUND_PHONE_OUTGOING);
1937 if (priv->call_state != REDIALING)
1938 priv->call_state = DISCONNECTED;
1940 if (could_reset_pipeline)
1942 g_mutex_lock (priv->lock);
1944 g_timer_stop (priv->timer);
1946 if (priv->timer_id != 0)
1947 g_source_remove (priv->timer_id);
1950 g_mutex_unlock (priv->lock);
1953 /* We are about to destroy the window, no need to update it or create
1954 * a video preview */
1957 empathy_streamed_media_window_status_message (self, _("Disconnected"));
1959 gtk_action_set_sensitive (priv->redial, TRUE);
1960 gtk_widget_set_sensitive (priv->redial_button, TRUE);
1962 /* Unsensitive the camera and mic button */
1963 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
1964 gtk_action_set_sensitive (priv->action_camera_on, FALSE);
1965 gtk_widget_set_sensitive (priv->mic_button, FALSE);
1967 /* Be sure that the mic button is enabled */
1968 gtk_toggle_tool_button_set_active (
1969 GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1971 if (priv->camera_state == CAMERA_STATE_ON)
1973 /* Enable the 'preview' button as we are not sending atm. */
1974 gtk_toggle_tool_button_set_active (
1975 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_preview), TRUE);
1977 else if (priv->camera_state == CAMERA_STATE_PREVIEW)
1979 /* Restart the preview with the new pipeline. */
1980 display_video_preview (self, TRUE);
1983 gtk_progress_bar_set_fraction (
1984 GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
1986 /* destroy the video output; it will be recreated when we'll redial */
1987 disconnect_video_output_motion_handler (self);
1988 gtk_widget_destroy (priv->video_output);
1989 priv->video_output = NULL;
1991 gtk_widget_show (priv->remote_user_avatar_widget);
1993 reset_details_pane (self);
1995 priv->sending_video = FALSE;
1996 priv->call_started = FALSE;
1998 could_disconnect = TRUE;
2000 /* TODO: display the self avatar of the preview (depends if the "Always
2001 * Show Video Preview" is enabled or not) */
2004 return could_disconnect;
2009 empathy_streamed_media_window_channel_closed_cb (EmpathyStreamedMediaHandler *handler,
2012 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2013 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2015 if (empathy_streamed_media_window_disconnected (self, TRUE) &&
2016 priv->call_state == REDIALING)
2017 empathy_streamed_media_window_restart_call (self);
2022 empathy_streamed_media_window_channel_stream_closed_cb (EmpathyStreamedMediaHandler *handler,
2023 TfStream *stream, gpointer user_data)
2025 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2026 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2029 g_object_get (stream, "media-type", &media_type, NULL);
2032 * This assumes that there is only one video stream per channel...
2035 if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
2037 if (priv->funnel != NULL)
2041 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
2042 (priv->video_output));
2044 gst_element_set_state (output, GST_STATE_NULL);
2045 gst_element_set_state (priv->funnel, GST_STATE_NULL);
2047 gst_bin_remove (GST_BIN (priv->pipeline), output);
2048 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2049 priv->funnel = NULL;
2052 else if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
2054 if (priv->liveadder != NULL)
2056 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2057 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
2059 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2060 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
2061 priv->liveadder = NULL;
2066 /* Called with global lock held */
2068 empathy_streamed_media_window_get_video_sink_pad (EmpathyStreamedMediaWindow *self)
2070 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2074 if (priv->funnel == NULL)
2076 output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
2077 (priv->video_output));
2079 priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
2083 g_warning ("Could not create fsfunnel");
2087 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->funnel))
2089 gst_object_unref (priv->funnel);
2090 priv->funnel = NULL;
2091 g_warning ("Could not add funnel to pipeline");
2095 if (!gst_bin_add (GST_BIN (priv->pipeline), output))
2097 g_warning ("Could not add the video output widget to the pipeline");
2101 if (!gst_element_link (priv->funnel, output))
2103 g_warning ("Could not link output sink to funnel");
2104 goto error_output_added;
2107 if (gst_element_set_state (output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2109 g_warning ("Could not start video sink");
2110 goto error_output_added;
2113 if (gst_element_set_state (priv->funnel, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2115 g_warning ("Could not start funnel");
2116 goto error_output_added;
2120 pad = gst_element_get_request_pad (priv->funnel, "sink%d");
2123 g_warning ("Could not get request pad from funnel");
2130 gst_element_set_locked_state (priv->funnel, TRUE);
2131 gst_element_set_locked_state (output, TRUE);
2133 gst_element_set_state (priv->funnel, GST_STATE_NULL);
2134 gst_element_set_state (output, GST_STATE_NULL);
2136 gst_bin_remove (GST_BIN (priv->pipeline), output);
2137 gst_element_set_locked_state (output, FALSE);
2141 gst_bin_remove (GST_BIN (priv->pipeline), priv->funnel);
2142 priv->funnel = NULL;
2147 /* Called with global lock held */
2149 empathy_streamed_media_window_get_audio_sink_pad (EmpathyStreamedMediaWindow *self)
2151 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2154 GError *gerror = NULL;
2156 if (priv->liveadder == NULL)
2158 priv->liveadder = gst_element_factory_make ("liveadder", NULL);
2160 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder))
2162 g_warning ("Could not add liveadder to the pipeline");
2163 goto error_add_liveadder;
2165 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output))
2167 g_warning ("Could not add audio sink to pipeline");
2168 goto error_add_output;
2171 if (gst_element_set_state (priv->liveadder, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2173 g_warning ("Could not start liveadder");
2177 if (gst_element_set_state (priv->audio_output, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2179 g_warning ("Could not start audio sink");
2183 if (GST_PAD_LINK_FAILED (
2184 gst_element_link (priv->liveadder, priv->audio_output)))
2186 g_warning ("Could not link liveadder to audio output");
2191 filter = gst_parse_bin_from_description (
2192 "audioconvert ! audioresample ! audioconvert", TRUE, &gerror);
2195 g_warning ("Could not make audio conversion filter: %s", gerror->message);
2196 g_clear_error (&gerror);
2200 if (!gst_bin_add (GST_BIN (priv->pipeline), filter))
2202 g_warning ("Could not add audio conversion filter to pipeline");
2203 gst_object_unref (filter);
2207 if (gst_element_set_state (filter, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2209 g_warning ("Could not start audio conversion filter");
2213 if (!gst_element_link (filter, priv->liveadder))
2215 g_warning ("Could not link audio conversion filter to liveadder");
2219 pad = gst_element_get_static_pad (filter, "sink");
2223 g_warning ("Could not get sink pad from filter");
2231 gst_element_set_locked_state (filter, TRUE);
2232 gst_element_set_state (filter, GST_STATE_NULL);
2233 gst_bin_remove (GST_BIN (priv->pipeline), filter);
2237 gst_element_set_locked_state (priv->liveadder, TRUE);
2238 gst_element_set_locked_state (priv->audio_output, TRUE);
2240 gst_element_set_state (priv->liveadder, GST_STATE_NULL);
2241 gst_element_set_state (priv->audio_output, GST_STATE_NULL);
2243 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_output);
2247 gst_bin_remove (GST_BIN (priv->pipeline), priv->liveadder);
2249 gst_element_set_locked_state (priv->liveadder, FALSE);
2250 gst_element_set_locked_state (priv->audio_output, FALSE);
2252 error_add_liveadder:
2254 if (priv->liveadder != NULL)
2256 gst_object_unref (priv->liveadder);
2257 priv->liveadder = NULL;
2264 empathy_streamed_media_window_update_timer (gpointer user_data)
2266 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2267 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2271 time_ = g_timer_elapsed (priv->timer, NULL);
2273 /* Translators: number of minutes:seconds the caller has been connected */
2274 str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time_ / 60,
2276 empathy_streamed_media_window_status_message (self, str);
2283 display_error (EmpathyStreamedMediaWindow *self,
2284 EmpathyTpStreamedMedia *call,
2288 const gchar *details)
2290 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2291 GtkWidget *info_bar;
2292 GtkWidget *content_area;
2299 /* Create info bar */
2300 info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2303 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
2305 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2307 /* hbox containing the image and the messages vbox */
2308 hbox = gtk_hbox_new (FALSE, 3);
2309 gtk_container_add (GTK_CONTAINER (content_area), hbox);
2312 image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
2313 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2315 /* vbox containing the main message and the details expander */
2316 vbox = gtk_vbox_new (FALSE, 3);
2317 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
2320 txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
2322 label = gtk_label_new (NULL);
2323 gtk_label_set_markup (GTK_LABEL (label), txt);
2324 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2325 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2328 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
2331 if (details != NULL)
2333 GtkWidget *expander;
2335 expander = gtk_expander_new (_("Technical Details"));
2337 txt = g_strdup_printf ("<i>%s</i>", details);
2339 label = gtk_label_new (NULL);
2340 gtk_label_set_markup (GTK_LABEL (label), txt);
2341 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2342 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
2345 gtk_container_add (GTK_CONTAINER (expander), label);
2346 gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
2349 g_signal_connect (info_bar, "response",
2350 G_CALLBACK (gtk_widget_destroy), NULL);
2352 gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
2353 FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
2354 gtk_widget_show_all (info_bar);
2358 media_stream_error_to_txt (EmpathyStreamedMediaWindow *self,
2359 EmpathyTpStreamedMedia *call,
2361 TpMediaStreamError error)
2363 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2370 case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED:
2372 return g_strdup_printf (
2373 _("%s's software does not understand any of the audio formats "
2374 "supported by your computer"),
2375 empathy_contact_get_alias (priv->contact));
2377 return g_strdup_printf (
2378 _("%s's software does not understand any of the video formats "
2379 "supported by your computer"),
2380 empathy_contact_get_alias (priv->contact));
2382 case TP_MEDIA_STREAM_ERROR_CONNECTION_FAILED:
2383 return g_strdup_printf (
2384 _("Can't establish a connection to %s. "
2385 "One of you might be on a network that does not allow "
2386 "direct connections."),
2387 empathy_contact_get_alias (priv->contact));
2389 case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
2390 return g_strdup (_("There was a failure on the network"));
2392 case TP_MEDIA_STREAM_ERROR_NO_CODECS:
2394 return g_strdup (_("The audio formats necessary for this call "
2395 "are not installed on your computer"));
2397 return g_strdup (_("The video formats necessary for this call "
2398 "are not installed on your computer"));
2400 case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
2401 cm = empathy_tp_streamed_media_get_connection_manager (call);
2403 url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
2404 "product=Telepathy&component=%s", cm);
2406 result = g_strdup_printf (
2407 _("Something unexpected happened in a Telepathy component. "
2408 "Please <a href=\"%s\">report this bug</a> and attach "
2409 "logs gathered from the 'Debug' window in the Help menu."), url);
2414 case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
2415 return g_strdup (_("There was a failure in the call engine"));
2417 case TP_MEDIA_STREAM_ERROR_EOS:
2418 return g_strdup (_("The end of the stream was reached"));
2420 case TP_MEDIA_STREAM_ERROR_UNKNOWN:
2427 empathy_streamed_media_window_stream_error (EmpathyStreamedMediaWindow *self,
2428 EmpathyTpStreamedMedia *call,
2437 desc = media_stream_error_to_txt (self, call, audio, code);
2440 /* No description, use the error message. That's not great as it's not
2441 * localized but it's better than nothing. */
2442 display_error (self, call, icon, title, msg, NULL);
2446 display_error (self, call, icon, title, desc, msg);
2452 empathy_streamed_media_window_audio_stream_error (EmpathyTpStreamedMedia *call,
2455 EmpathyStreamedMediaWindow *self)
2457 empathy_streamed_media_window_stream_error (self, call, TRUE, code, msg,
2458 "gnome-stock-mic", _("Can't establish audio stream"));
2462 empathy_streamed_media_window_video_stream_error (EmpathyTpStreamedMedia *call,
2465 EmpathyStreamedMediaWindow *self)
2467 empathy_streamed_media_window_stream_error (self, call, FALSE, code, msg,
2468 "camera-web", _("Can't establish video stream"));
2472 empathy_streamed_media_window_connected (gpointer user_data)
2474 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2475 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2476 EmpathyTpStreamedMedia *call;
2477 gboolean can_send_video;
2479 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2481 can_send_video = priv->video_input != NULL && priv->contact != NULL &&
2482 empathy_contact_can_voip_video (priv->contact);
2484 g_object_get (priv->handler, "tp-call", &call, NULL);
2486 tp_g_signal_connect_object (call, "notify::video-stream",
2487 G_CALLBACK (empathy_streamed_media_window_video_stream_changed_cb),
2490 if (empathy_tp_streamed_media_has_dtmf (call))
2491 gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
2493 if (priv->video_input == NULL)
2494 empathy_streamed_media_window_set_send_video (self, CAMERA_STATE_OFF);
2496 priv->sending_video = can_send_video ?
2497 empathy_tp_streamed_media_is_sending_video (call) : FALSE;
2499 gtk_toggle_tool_button_set_active (
2500 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
2501 priv->sending_video && priv->video_input != NULL);
2502 gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
2503 gtk_action_set_sensitive (priv->action_camera_on, can_send_video);
2505 gtk_action_set_sensitive (priv->redial, FALSE);
2506 gtk_widget_set_sensitive (priv->redial_button, FALSE);
2508 gtk_widget_set_sensitive (priv->mic_button, TRUE);
2510 empathy_streamed_media_window_update_avatars_visibility (call, self);
2512 g_object_unref (call);
2514 g_mutex_lock (priv->lock);
2516 priv->timer_id = g_timeout_add_seconds (1,
2517 empathy_streamed_media_window_update_timer, self);
2519 g_mutex_unlock (priv->lock);
2521 empathy_streamed_media_window_update_timer (self);
2523 gtk_action_set_sensitive (priv->menu_fullscreen, TRUE);
2529 /* Called from the streaming thread */
2531 empathy_streamed_media_window_src_added_cb (EmpathyStreamedMediaHandler *handler,
2532 GstPad *src, guint media_type, gpointer user_data)
2534 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2535 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2536 gboolean retval = FALSE;
2540 g_mutex_lock (priv->lock);
2542 if (priv->call_state != CONNECTED)
2544 g_timer_start (priv->timer);
2545 priv->timer_id = g_idle_add (empathy_streamed_media_window_connected, self);
2546 priv->call_state = CONNECTED;
2551 case TP_MEDIA_STREAM_TYPE_AUDIO:
2552 pad = empathy_streamed_media_window_get_audio_sink_pad (self);
2554 case TP_MEDIA_STREAM_TYPE_VIDEO:
2555 gtk_widget_hide (priv->remote_user_avatar_widget);
2556 gtk_widget_show (priv->video_output);
2557 pad = empathy_streamed_media_window_get_video_sink_pad (self);
2560 g_assert_not_reached ();
2566 if (GST_PAD_LINK_FAILED (gst_pad_link (src, pad)))
2567 g_warning ("Could not link %s sink pad",
2568 media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
2572 gst_object_unref (pad);
2576 /* If no sink could be linked, try to add fakesink to prevent the whole call
2581 GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
2583 if (gst_bin_add (GST_BIN (priv->pipeline), fakesink))
2585 GstPad *sinkpad = gst_element_get_static_pad (fakesink, "sink");
2586 if (gst_element_set_state (fakesink, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE ||
2587 GST_PAD_LINK_FAILED (gst_pad_link (src, sinkpad)))
2589 gst_element_set_locked_state (fakesink, TRUE);
2590 gst_element_set_state (fakesink, GST_STATE_NULL);
2591 gst_bin_remove (GST_BIN (priv->pipeline), fakesink);
2595 g_debug ("Could not link real sink, linked fakesink instead");
2597 gst_object_unref (sinkpad);
2601 gst_object_unref (fakesink);
2606 g_mutex_unlock (priv->lock);
2612 empathy_streamed_media_window_sink_added_cb (EmpathyStreamedMediaHandler *handler,
2613 GstPad *sink, guint media_type, gpointer user_data)
2615 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2616 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2618 gboolean retval = FALSE;
2622 case TP_MEDIA_STREAM_TYPE_AUDIO:
2623 if (!gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input))
2625 g_warning ("Could not add audio source to pipeline");
2629 pad = gst_element_get_static_pad (priv->audio_input, "src");
2632 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2633 g_warning ("Could not get source pad from audio source");
2637 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2639 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2640 g_warning ("Could not link audio source to farsight");
2644 if (gst_element_set_state (priv->audio_input, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
2646 g_warning ("Could not start audio source");
2647 gst_element_set_state (priv->audio_input, GST_STATE_NULL);
2648 gst_bin_remove (GST_BIN (priv->pipeline), priv->audio_input);
2654 case TP_MEDIA_STREAM_TYPE_VIDEO:
2655 if (priv->video_input != NULL)
2657 if (priv->video_tee != NULL)
2659 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
2660 if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sink)))
2662 g_warning ("Could not link videp soure input pipeline");
2665 gst_object_unref (pad);
2672 g_assert_not_reached ();
2679 empathy_streamed_media_window_remove_video_input (EmpathyStreamedMediaWindow *self)
2681 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2682 GstElement *preview;
2684 disable_camera (self);
2686 DEBUG ("remove video input");
2687 preview = empathy_video_widget_get_element (
2688 EMPATHY_VIDEO_WIDGET (priv->video_preview));
2690 gst_element_set_state (priv->video_input, GST_STATE_NULL);
2691 gst_element_set_state (priv->video_tee, GST_STATE_NULL);
2692 gst_element_set_state (preview, GST_STATE_NULL);
2694 gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
2695 priv->video_tee, preview, NULL);
2697 g_object_unref (priv->video_input);
2698 priv->video_input = NULL;
2699 g_object_unref (priv->video_tee);
2700 priv->video_tee = NULL;
2701 gtk_widget_destroy (priv->video_preview);
2702 priv->video_preview = NULL;
2704 gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
2705 gtk_action_set_sensitive (priv->action_camera_on, FALSE);
2706 gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
2710 start_call (EmpathyStreamedMediaWindow *self)
2712 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2714 priv->call_started = TRUE;
2715 empathy_streamed_media_handler_start_call (priv->handler,
2716 empathy_get_current_action_time ());
2718 if (empathy_streamed_media_handler_has_initial_video (priv->handler))
2720 /* Enable 'send video' buttons and display the preview */
2721 gtk_toggle_tool_button_set_active (
2722 GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
2727 empathy_streamed_media_window_bus_message (GstBus *bus, GstMessage *message,
2730 EmpathyStreamedMediaWindow *self = EMPATHY_STREAMED_MEDIA_WINDOW (user_data);
2731 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2734 empathy_streamed_media_handler_bus_message (priv->handler, bus, message);
2736 switch (GST_MESSAGE_TYPE (message))
2738 case GST_MESSAGE_STATE_CHANGED:
2739 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
2741 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2742 if (newstate == GST_STATE_PAUSED)
2743 empathy_streamed_media_window_setup_video_input (self);
2745 if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
2746 !priv->call_started)
2748 gst_message_parse_state_changed (message, NULL, &newstate, NULL);
2749 if (newstate == GST_STATE_PAUSED)
2751 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2752 priv->pipeline_playing = TRUE;
2754 if (priv->start_call_when_playing)
2759 case GST_MESSAGE_ERROR:
2761 GError *error = NULL;
2762 GstElement *gst_error;
2765 gst_message_parse_error (message, &error, &debug);
2766 gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
2768 g_message ("Element error: %s -- %s\n", error->message, debug);
2770 if (g_str_has_prefix (gst_element_get_name (gst_error),
2771 VIDEO_INPUT_ERROR_PREFIX))
2773 /* Remove the video input and continue */
2774 if (priv->video_input != NULL)
2775 empathy_streamed_media_window_remove_video_input (self);
2776 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
2780 empathy_streamed_media_window_disconnected (self, TRUE);
2782 g_error_free (error);
2785 case GST_MESSAGE_UNKNOWN:
2786 case GST_MESSAGE_EOS:
2787 case GST_MESSAGE_WARNING:
2788 case GST_MESSAGE_INFO:
2789 case GST_MESSAGE_TAG:
2790 case GST_MESSAGE_BUFFERING:
2791 case GST_MESSAGE_STATE_DIRTY:
2792 case GST_MESSAGE_STEP_DONE:
2793 case GST_MESSAGE_CLOCK_PROVIDE:
2794 case GST_MESSAGE_CLOCK_LOST:
2795 case GST_MESSAGE_NEW_CLOCK:
2796 case GST_MESSAGE_STRUCTURE_CHANGE:
2797 case GST_MESSAGE_STREAM_STATUS:
2798 case GST_MESSAGE_APPLICATION:
2799 case GST_MESSAGE_ELEMENT:
2800 case GST_MESSAGE_SEGMENT_START:
2801 case GST_MESSAGE_SEGMENT_DONE:
2802 case GST_MESSAGE_DURATION:
2803 case GST_MESSAGE_ANY:
2812 empathy_streamed_media_window_update_avatars_visibility (EmpathyTpStreamedMedia *call,
2813 EmpathyStreamedMediaWindow *window)
2815 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2817 if (empathy_tp_streamed_media_is_receiving_video (call))
2819 gtk_widget_hide (priv->remote_user_avatar_widget);
2820 gtk_widget_show (priv->video_output);
2824 gtk_widget_hide (priv->video_output);
2825 gtk_widget_show (priv->remote_user_avatar_widget);
2830 call_handler_notify_tp_streamed_media_cb (EmpathyStreamedMediaHandler *handler,
2832 EmpathyStreamedMediaWindow *self)
2834 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
2835 EmpathyTpStreamedMedia *call;
2837 g_object_get (priv->handler, "tp-call", &call, NULL);
2841 tp_g_signal_connect_object (call, "audio-stream-error",
2842 G_CALLBACK (empathy_streamed_media_window_audio_stream_error), self, 0);
2843 tp_g_signal_connect_object (call, "video-stream-error",
2844 G_CALLBACK (empathy_streamed_media_window_video_stream_error), self, 0);
2846 g_object_unref (call);
2850 empathy_streamed_media_window_realized_cb (GtkWidget *widget, EmpathyStreamedMediaWindow *window)
2852 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2853 EmpathyTpStreamedMedia *call;
2855 g_signal_connect (priv->handler, "conference-added",
2856 G_CALLBACK (empathy_streamed_media_window_conference_added_cb), window);
2857 g_signal_connect (priv->handler, "request-resource",
2858 G_CALLBACK (empathy_streamed_media_window_request_resource_cb), window);
2859 g_signal_connect (priv->handler, "closed",
2860 G_CALLBACK (empathy_streamed_media_window_channel_closed_cb), window);
2861 g_signal_connect (priv->handler, "src-pad-added",
2862 G_CALLBACK (empathy_streamed_media_window_src_added_cb), window);
2863 g_signal_connect (priv->handler, "sink-pad-added",
2864 G_CALLBACK (empathy_streamed_media_window_sink_added_cb), window);
2865 g_signal_connect (priv->handler, "stream-closed",
2866 G_CALLBACK (empathy_streamed_media_window_channel_stream_closed_cb), window);
2868 g_object_get (priv->handler, "tp-call", &call, NULL);
2871 tp_g_signal_connect_object (call, "audio-stream-error",
2872 G_CALLBACK (empathy_streamed_media_window_audio_stream_error), window,
2874 tp_g_signal_connect_object (call, "video-stream-error",
2875 G_CALLBACK (empathy_streamed_media_window_video_stream_error), window,
2878 g_object_unref (call);
2882 /* tp-call doesn't exist yet, we'll connect signals once it has been
2884 g_signal_connect (priv->handler, "notify::tp-call",
2885 G_CALLBACK (call_handler_notify_tp_streamed_media_cb), window);
2888 gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
2892 empathy_streamed_media_window_delete_cb (GtkWidget *widget, GdkEvent*event,
2893 EmpathyStreamedMediaWindow *window)
2895 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2897 if (priv->pipeline != NULL)
2899 if (priv->bus_message_source_id != 0)
2901 g_source_remove (priv->bus_message_source_id);
2902 priv->bus_message_source_id = 0;
2905 gst_element_set_state (priv->pipeline, GST_STATE_NULL);
2908 if (priv->call_state == CONNECTING)
2909 empathy_sound_manager_stop (priv->sound_mgr, EMPATHY_SOUND_PHONE_OUTGOING);
2915 show_controls (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2918 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2920 menu = gtk_ui_manager_get_widget (priv->ui_manager,
2925 gtk_widget_hide (priv->sidebar);
2926 gtk_widget_hide (menu);
2927 gtk_widget_hide (priv->vbox);
2928 gtk_widget_hide (priv->statusbar);
2929 gtk_widget_hide (priv->toolbar);
2933 if (priv->sidebar_was_visible_before_fs)
2934 gtk_widget_show (priv->sidebar);
2936 gtk_widget_show (menu);
2937 gtk_widget_show (priv->vbox);
2938 gtk_widget_show (priv->statusbar);
2939 gtk_widget_show (priv->toolbar);
2941 gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
2942 priv->original_height_before_fs);
2947 show_borders (EmpathyStreamedMediaWindow *window, gboolean set_fullscreen)
2949 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2951 gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
2952 set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
2953 gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
2954 set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
2956 if (priv->video_output != NULL)
2958 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2959 priv->video_output, TRUE, TRUE,
2960 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2964 gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
2965 priv->vbox, TRUE, TRUE,
2966 set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
2971 empathy_streamed_media_window_state_event_cb (GtkWidget *widget,
2972 GdkEventWindowState *event, EmpathyStreamedMediaWindow *window)
2974 if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
2976 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
2977 gboolean set_fullscreen = event->new_window_state &
2978 GDK_WINDOW_STATE_FULLSCREEN;
2982 gboolean sidebar_was_visible;
2983 GtkAllocation allocation;
2984 gint original_width, original_height;
2986 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
2987 original_width = allocation.width;
2988 original_height = allocation.height;
2990 g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
2992 priv->sidebar_was_visible_before_fs = sidebar_was_visible;
2993 priv->original_width_before_fs = original_width;
2994 priv->original_height_before_fs = original_height;
2996 if (priv->video_output_motion_handler_id == 0 &&
2997 priv->video_output != NULL)
2999 priv->video_output_motion_handler_id = g_signal_connect (
3000 G_OBJECT (priv->video_output), "motion-notify-event",
3001 G_CALLBACK (empathy_streamed_media_window_video_output_motion_notify),
3007 disconnect_video_output_motion_handler (window);
3010 empathy_streamed_media_window_fullscreen_set_fullscreen (priv->fullscreen,
3012 show_controls (window, set_fullscreen);
3013 show_borders (window, set_fullscreen);
3014 gtk_action_set_stock_id (priv->menu_fullscreen,
3015 (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
3016 priv->is_fullscreen = set_fullscreen;
3023 empathy_streamed_media_window_sidebar_toggled_cb (GtkToggleButton *toggle,
3024 EmpathyStreamedMediaWindow *window)
3026 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3028 int w, h, handle_size;
3029 GtkAllocation allocation, sidebar_allocation;
3031 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
3032 w = allocation.width;
3033 h = allocation.height;
3035 gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
3037 gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
3038 if (gtk_toggle_button_get_active (toggle))
3040 arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
3041 gtk_widget_show (priv->sidebar);
3042 w += sidebar_allocation.width + handle_size;
3046 arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
3047 w -= sidebar_allocation.width + handle_size;
3048 gtk_widget_hide (priv->sidebar);
3051 gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
3054 gtk_window_resize (GTK_WINDOW (window), w, h);
3058 empathy_streamed_media_window_set_send_video (EmpathyStreamedMediaWindow *window,
3061 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3062 EmpathyTpStreamedMedia *call;
3064 priv->sending_video = (state == CAMERA_STATE_ON);
3066 if (state == CAMERA_STATE_PREVIEW ||
3067 state == CAMERA_STATE_ON)
3069 /* When we start sending video, we want to show the video preview by
3071 display_video_preview (window, TRUE);
3075 display_video_preview (window, FALSE);
3078 if (priv->call_state != CONNECTED)
3081 g_object_get (priv->handler, "tp-call", &call, NULL);
3082 DEBUG ("%s sending video", priv->sending_video ? "start": "stop");
3083 empathy_tp_streamed_media_request_video_stream_direction (call, priv->sending_video);
3084 g_object_unref (call);
3088 empathy_streamed_media_window_mic_toggled_cb (GtkToggleToolButton *toggle,
3089 EmpathyStreamedMediaWindow *window)
3091 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3094 active = (gtk_toggle_tool_button_get_active (toggle));
3098 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3100 gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
3104 /* TODO, Instead of setting the input volume to 0 we should probably
3105 * stop sending but this would cause the audio call to drop if both
3106 * sides mute at the same time on certain CMs AFAIK. Need to revisit this
3107 * in the future. GNOME #574574
3109 empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
3111 gtk_adjustment_set_value (priv->audio_input_adj, 0);
3116 empathy_streamed_media_window_sidebar_hidden_cb (EvSidebar *sidebar,
3117 EmpathyStreamedMediaWindow *window)
3119 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3121 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
3126 empathy_streamed_media_window_sidebar_shown_cb (EvSidebar *sidebar,
3127 EmpathyStreamedMediaWindow *window)
3129 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3131 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
3136 empathy_streamed_media_window_hangup_cb (gpointer object,
3137 EmpathyStreamedMediaWindow *window)
3139 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3141 empathy_streamed_media_handler_stop_call (priv->handler);
3143 if (empathy_streamed_media_window_disconnected (window, FALSE))
3144 gtk_widget_destroy (GTK_WIDGET (window));
3148 empathy_streamed_media_window_restart_call (EmpathyStreamedMediaWindow *window)
3150 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3152 /* Remove error info bars */
3153 gtk_container_forall (GTK_CONTAINER (priv->errors_vbox),
3154 (GtkCallback) gtk_widget_destroy, NULL);
3156 create_video_output_widget (window);
3158 g_signal_connect (G_OBJECT (priv->audio_input_adj), "value-changed",
3159 G_CALLBACK (empathy_streamed_media_window_mic_volume_changed_cb), window);
3161 /* While the call was disconnected, the input volume might have changed.
3162 * However, since the audio_input source was destroyed, its volume has not
3163 * been updated during that time. That's why we manually update it here */
3164 empathy_streamed_media_window_mic_volume_changed_cb (priv->audio_input_adj, window);
3166 priv->outgoing = TRUE;
3167 empathy_streamed_media_window_set_state_connecting (window);
3169 if (priv->pipeline_playing)
3170 start_call (window);
3172 /* call will be started when the pipeline is ready */
3173 priv->start_call_when_playing = TRUE;
3176 empathy_streamed_media_window_setup_avatars (window, priv->handler);
3178 gtk_action_set_sensitive (priv->redial, FALSE);
3179 gtk_widget_set_sensitive (priv->redial_button, FALSE);
3183 empathy_streamed_media_window_redial_cb (gpointer object,
3184 EmpathyStreamedMediaWindow *window)
3186 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3188 if (priv->call_state == CONNECTED)
3189 priv->call_state = REDIALING;
3191 empathy_streamed_media_handler_stop_call (priv->handler);
3193 if (priv->call_state != CONNECTED)
3194 empathy_streamed_media_window_restart_call (window);
3198 empathy_streamed_media_window_fullscreen_cb (gpointer object,
3199 EmpathyStreamedMediaWindow *window)
3201 empathy_streamed_media_window_fullscreen_toggle (window);
3205 empathy_streamed_media_window_fullscreen_toggle (EmpathyStreamedMediaWindow *window)
3207 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3209 if (priv->is_fullscreen)
3210 gtk_window_unfullscreen (GTK_WINDOW (window));
3212 gtk_window_fullscreen (GTK_WINDOW (window));
3216 empathy_streamed_media_window_video_button_press_cb (GtkWidget *video_output,
3217 GdkEventButton *event, EmpathyStreamedMediaWindow *window)
3219 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
3221 empathy_streamed_media_window_video_menu_popup (window, event->button);
3229 empathy_streamed_media_window_key_press_cb (GtkWidget *video_output,
3230 GdkEventKey *event, EmpathyStreamedMediaWindow *window)
3232 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3234 if (priv->is_fullscreen && event->keyval == GDK_KEY_Escape)
3236 /* Since we are in fullscreen mode, toggling will bring us back to
3238 empathy_streamed_media_window_fullscreen_toggle (window);
3246 empathy_streamed_media_window_video_output_motion_notify (GtkWidget *widget,
3247 GdkEventMotion *event, EmpathyStreamedMediaWindow *window)
3249 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3251 if (priv->is_fullscreen)
3253 empathy_streamed_media_window_fullscreen_show_popup (priv->fullscreen);
3260 empathy_streamed_media_window_video_menu_popup (EmpathyStreamedMediaWindow *window,
3264 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3266 menu = gtk_ui_manager_get_widget (priv->ui_manager,
3268 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
3269 button, gtk_get_current_event_time ());
3270 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
3274 empathy_streamed_media_window_status_message (EmpathyStreamedMediaWindow *window,
3277 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3279 if (priv->context_id == 0)
3281 priv->context_id = gtk_statusbar_get_context_id (
3282 GTK_STATUSBAR (priv->statusbar), "voip call status messages");
3286 gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
3289 gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
3294 empathy_streamed_media_window_volume_changed_cb (GtkScaleButton *button,
3295 gdouble value, EmpathyStreamedMediaWindow *window)
3297 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (window);
3299 if (priv->audio_output == NULL)
3302 empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
3306 /* block all the signals related to camera control widgets. This is useful
3307 * when we are manually updating the UI and so don't want to fire the
3310 block_camera_control_signals (EmpathyStreamedMediaWindow *self)
3312 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3314 g_signal_handlers_block_by_func (priv->tool_button_camera_off,
3315 tool_button_camera_off_toggled_cb, self);
3316 g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
3317 tool_button_camera_preview_toggled_cb, self);
3318 g_signal_handlers_block_by_func (priv->tool_button_camera_on,
3319 tool_button_camera_on_toggled_cb, self);
3320 g_signal_handlers_block_by_func (priv->action_camera_on,
3321 action_camera_change_cb, self);
3325 unblock_camera_control_signals (EmpathyStreamedMediaWindow *self)
3327 EmpathyStreamedMediaWindowPriv *priv = GET_PRIV (self);
3329 g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
3330 tool_button_camera_off_toggled_cb, self);
3331 g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
3332 tool_button_camera_preview_toggled_cb, self);
3333 g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
3334 tool_button_camera_on_toggled_cb, self);
3335 g_signal_handlers_unblock_by_func (priv->action_camera_on,
3336 action_camera_change_cb, self);