video_widget.cpp 23.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
/*
 *  Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
 */

#include "video_widget.h"

#include <clutter/clutter.h>
#include <clutter-gtk/clutter-gtk.h>
#include <video/renderer.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
36
#include <video/sourcemodel.h>
37 38 39
#include <video/devicemodel.h>
#include <QtCore/QUrl>
#include "../defines.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
40
#include <stdlib.h>
Guillaume Roguez's avatar
Guillaume Roguez committed
41
#include <atomic>
42
#include <mutex>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
43
#include "xrectsel.h"
44

Stepan Salenikovich's avatar
Stepan Salenikovich committed
45 46 47
#define VIDEO_LOCAL_SIZE            150
#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */

48 49 50 51 52
/* check video frame queues at this rate;
 * use 30 ms (about 30 fps) since we don't expect to
 * receive video frames faster than that */
#define FRAME_RATE_PERIOD           30

53 54 55 56 57 58 59 60 61 62
struct _VideoWidgetClass {
    GtkBinClass parent_class;
};

struct _VideoWidget {
    GtkBin parent;
};

typedef struct _VideoWidgetPrivate VideoWidgetPrivate;

Stepan Salenikovich's avatar
Stepan Salenikovich committed
63 64
typedef struct _VideoWidgetRenderer VideoWidgetRenderer;

65 66 67 68 69 70
struct _VideoWidgetPrivate {
    GtkWidget               *clutter_widget;
    ClutterActor            *stage;
    ClutterActor            *video_container;

    /* remote peer data */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
71
    VideoWidgetRenderer     *remote;
72 73

    /* local peer data */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
74
    VideoWidgetRenderer     *local;
75 76

    guint                    frame_timeout_source;
77 78 79 80 81 82 83 84

    /* new renderers should be put into the queue for processing by a g_timeout
     * function whose id should be saved into renderer_timeout_source;
     * this way when the VideoWidget object is destroyed, we do not try
     * to process any new renderers by stoping the g_timeout function.
     */
    guint                    renderer_timeout_source;
    GAsyncQueue             *new_renderer_queue;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
85 86 87
};

struct _VideoWidgetRenderer {
88
    VideoRendererType        type;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
89 90
    ClutterActor            *actor;
    Video::Renderer         *renderer;
91 92
    std::mutex               run_mutex;
    bool                     running;
Guillaume Roguez's avatar
Guillaume Roguez committed
93
    std::atomic_bool         dirty;
94 95 96 97 98 99 100

    /* show_black_frame is used to request the actor to render a black image;
     * this will take over 'running' and 'dirty', ie: a black frame will be
     * rendered even if the Video::Renderer is not running, or has a frame available.
     * this will be set back to false once the black frame is rendered
     */
    std::atomic_bool         show_black_frame;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
101 102 103
    QMetaObject::Connection  frame_update;
    QMetaObject::Connection  render_stop;
    QMetaObject::Connection  render_start;
104 105 106 107 108 109
};

G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);

#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))

110
/* static prototypes */
111 112 113 114
static gboolean check_frame_queue              (VideoWidget *);
static void     renderer_stop                  (VideoWidgetRenderer *);
static void     renderer_start                 (VideoWidgetRenderer *);
static void     on_drag_data_received          (GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
115
static gboolean on_button_press_in_screen_event(GtkWidget *, GdkEventButton *, gpointer);
116 117 118
static gboolean check_renderer_queue           (VideoWidget *);
static void     free_video_widget_renderer     (VideoWidgetRenderer *);
static void     video_widget_add_renderer      (VideoWidget *, VideoWidgetRenderer *);
119

120 121 122 123 124 125
/*
 * video_widget_dispose()
 *
 * The dispose function for the video_widget class.
 */
static void
Stepan Salenikovich's avatar
Stepan Salenikovich committed
126
video_widget_dispose(GObject *object)
127
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
128
    VideoWidget *self = VIDEO_WIDGET(object);
129 130
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

131 132 133 134 135 136 137
    /* dispose may be called multiple times, make sure
     * not to call g_source_remove more than once */
    if (priv->frame_timeout_source) {
        g_source_remove(priv->frame_timeout_source);
        priv->frame_timeout_source = 0;
    }

138 139 140 141 142 143 144 145 146 147
    if (priv->renderer_timeout_source) {
        g_source_remove(priv->renderer_timeout_source);
        priv->renderer_timeout_source = 0;
    }

    if (priv->new_renderer_queue) {
        g_async_queue_unref(priv->new_renderer_queue);
        priv->new_renderer_queue = NULL;
    }

Stepan Salenikovich's avatar
Stepan Salenikovich committed
148
    G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
149 150 151 152 153 154 155 156 157 158 159
}


/*
 * video_widget_finalize()
 *
 * The finalize function for the video_widget class.
 */
static void
video_widget_finalize(GObject *object)
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
160 161 162
    VideoWidget *self = VIDEO_WIDGET(object);
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

163 164
    free_video_widget_renderer(priv->local);
    free_video_widget_renderer(priv->remote);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
165

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
}


/*
 * video_widget_class_init()
 *
 * This function init the video_widget_class.
 */
static void
video_widget_class_init(VideoWidgetClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    /* override method */
    object_class->dispose = video_widget_dispose;
    object_class->finalize = video_widget_finalize;
}


/*
 * video_widget_init()
 *
 * This function init the video_widget.
 * - init clutter
 * - init all the widget members
 */
static void
video_widget_init(VideoWidget *self)
{
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

    /* init clutter widget */
    priv->clutter_widget = gtk_clutter_embed_new();
    /* add it to the video_widget */
    gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
    /* get the stage */
    priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));

    /* layout manager is used to arrange children in space, here we ask clutter
     * to align children to fill the space when resizing the window */
    clutter_actor_set_layout_manager(priv->stage,
        clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));

    /* add a scene container where we can add and remove our actors */
    priv->video_container = clutter_actor_new();
    clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
    clutter_actor_add_child(priv->stage, priv->video_container);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
    /* init the remote and local structs */
    priv->remote = g_new0(VideoWidgetRenderer, 1);
    priv->local = g_new0(VideoWidgetRenderer, 1);

    /* arrange remote actors */
    priv->remote->actor = clutter_actor_new();
    clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
    /* the remote camera must always fill the container size */
    ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
                                                                CLUTTER_BIND_SIZE, 0);
    clutter_actor_add_constraint(priv->remote->actor, constraint);

    /* arrange local actor */
    priv->local->actor = clutter_actor_new();
    clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
    /* set size to square, but it will stay the aspect ratio when the image is rendered */
    clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
    /* set position constraint to right cornder */
    constraint = clutter_align_constraint_new(priv->video_container,
                                              CLUTTER_ALIGN_BOTH, 0.99);
    clutter_actor_add_constraint(priv->local->actor, constraint);
    clutter_actor_set_opacity(priv->local->actor,
                              VIDEO_LOCAL_OPACITY_DEFAULT);

239 240 241 242 243 244 245 246 247 248
    /* Init the timeout source which will check the for new frames.
     * The priority must be lower than GTK drawing events
     * (G_PRIORITY_HIGH_IDLE + 20) so that this timeout source doesn't choke
     * the main loop on slower machines.
     */
    priv->frame_timeout_source = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
                                                    FRAME_RATE_PERIOD,
                                                    (GSourceFunc)check_frame_queue,
                                                    self,
                                                    NULL);
249

250
    /* init new renderer queue */
251
    priv->new_renderer_queue = g_async_queue_new_full((GDestroyNotify)free_video_widget_renderer);
252 253 254 255 256 257 258 259
    /* check new render every 30 ms (30ms is "fast enough");
     * we don't use an idle function so it doesn't consume cpu needlessly */
    priv->renderer_timeout_source= g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
                                                      30,
                                                      (GSourceFunc)check_renderer_queue,
                                                      self,
                                                      NULL);

260 261
    /* handle button event */
    g_signal_connect(GTK_WIDGET(self), "button-press-event", G_CALLBACK(on_button_press_in_screen_event), NULL);
262

263
    /* drag & drop files as video sources */
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
    gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
    g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
}

/*
 * on_drag_data_received()
 *
 * Handle dragged data in the video widget window.
 * Dropping an image causes the client to switch the video input to that image.
 */
static void
on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
                      G_GNUC_UNUSED GdkDragContext *context,
                      G_GNUC_UNUSED gint x,
                      G_GNUC_UNUSED gint y,
                      GtkSelectionData *selection_data,
                      G_GNUC_UNUSED guint info,
                      G_GNUC_UNUSED guint32 time,
                      G_GNUC_UNUSED gpointer data)
{
    gchar **uris = gtk_selection_data_get_uris(selection_data);

    /* only play the first selection */
    if (uris && *uris)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
289
        Video::SourceModel::instance()->setFile(QUrl(*uris));
290 291

    g_strfreev(uris);
292 293
}

294 295 296
static void
switch_video_input(G_GNUC_UNUSED GtkWidget *widget, Video::Device *device)
{
297
    Video::DeviceModel::instance()->setActive(device);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
298
    Video::SourceModel::instance()->switchTo(device);
299 300
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
301
static void
302
switch_video_input_screen(G_GNUC_UNUSED GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
{
    unsigned x, y;
    unsigned width, height;

    /* try to get the dispaly or default to 0 */
    QString display_env{getenv("DISPLAY")};
    int display = 0;

    if (!display_env.isEmpty()) {
        auto list = display_env.split(":", QString::SkipEmptyParts);
        /* should only be one display, so get the first one */
        if (list.size() > 0) {
            display = list.at(0).toInt();
            g_debug("sharing screen from DISPLAY %d", display);
        }
    }

    x = y = width = height = 0;

    xrectsel(&x, &y, &width, &height);

    if (!width || !height) {
        x = y = 0;
        width = gdk_screen_width();
        height = gdk_screen_height();
    }

Stepan Salenikovich's avatar
Stepan Salenikovich committed
330
    Video::SourceModel::instance()->setDisplay(display, QRect(x,y,width,height));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
331 332
}

333
static void
334
switch_video_input_file(G_GNUC_UNUSED GtkWidget *item, GtkWidget *parent)
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
{
    if (parent && GTK_IS_WIDGET(parent)) {
        /* get parent window */
        parent = gtk_widget_get_toplevel(GTK_WIDGET(parent));
    }

    gchar *uri = NULL;
    GtkWidget *dialog = gtk_file_chooser_dialog_new(
            "Choose File",
            GTK_WINDOW(parent),
            GTK_FILE_CHOOSER_ACTION_OPEN,
            "_Cancel", GTK_RESPONSE_CANCEL,
            "_Open", GTK_RESPONSE_ACCEPT,
            NULL);

    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
        uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
    }

    gtk_widget_destroy(dialog);

    Video::SourceModel::instance()->setFile(QUrl(uri));

    g_free(uri);
}

361 362 363 364 365 366
/*
 * on_button_press_in_screen_event()
 *
 * Handle button event in the video screen.
 */
static gboolean
367
on_button_press_in_screen_event(GtkWidget *parent,
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
                                GdkEventButton *event,
                                G_GNUC_UNUSED gpointer data)
{
    /* check for right click */
    if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
        return FALSE;

    /* create menu with available video sources */
    GtkWidget *menu = gtk_menu_new();

    /* list available devices and check off the active device */
    auto device_list = Video::DeviceModel::instance()->devices();

    for( auto device: device_list) {
        GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(device->name().toLocal8Bit().constData());
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), device->isActive());
        g_signal_connect(item, "activate", G_CALLBACK(switch_video_input), device);
    }

388 389 390
    /* add separator */
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());

Stepan Salenikovich's avatar
Stepan Salenikovich committed
391 392 393 394 395
    /* add screen area as an input */
    GtkWidget *item = gtk_check_menu_item_new_with_mnemonic("Share screen area");
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_screen), NULL);

396 397 398 399 400
    /* add file as an input */
    item = gtk_check_menu_item_new_with_mnemonic("Share file");
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    g_signal_connect(item, "activate", G_CALLBACK(switch_video_input_file), parent);

401 402 403 404 405 406 407
    /* show menu */
    gtk_widget_show_all(menu);
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);

    return TRUE; /* event has been fully handled */
}

Guillaume Roguez's avatar
Guillaume Roguez committed
408 409
static void
clutter_render_image(VideoWidgetRenderer* wg_renderer)
410
{
411 412 413 414 415 416 417
    auto actor = wg_renderer->actor;
    g_return_if_fail(CLUTTER_IS_ACTOR(actor));

    if (wg_renderer->show_black_frame) {
        /* render a black frame set the bool back to false, this is likely done
         * when the renderer is stopped so we ignore whether or not it is running
         */
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
        if (auto image_old = clutter_actor_get_content(actor)) {
            gfloat width;
            gfloat height;
            if (clutter_content_get_preferred_size(image_old, &width, &height)) {
                /* NOTE: this is a workaround for #72531, a crash which occurs
                 * in cogl < 1.18. We allocate a black frame of the same size
                 * as the previous image, instead of simply setting an empty or
                 * a NULL ClutterImage.
                 */
                auto image_empty = clutter_image_new();
                if (auto empty_data = (guint8 *)g_try_malloc0((gsize)width * height * 4)) {
                    GError* error = NULL;
                    clutter_image_set_data(
                            CLUTTER_IMAGE(image_empty),
                            empty_data,
                            COGL_PIXEL_FORMAT_BGRA_8888,
                            (guint)width,
                            (guint)height,
                            (guint)width*4,
                            &error);
                    if (error) {
                        g_warning("error rendering empty image to clutter: %s", error->message);
                        g_clear_error(&error);
                        g_object_unref(image_empty);
                        return;
                    }
                    clutter_actor_set_content(actor, image_empty);
                    g_object_unref(image_empty);
                    g_free(empty_data);
                } else {
                    clutter_actor_set_content(actor, NULL);
                }
            } else {
                clutter_actor_set_content(actor, NULL);
            }
        }
454 455 456 457
        wg_renderer->show_black_frame = false;
        return;
    }

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
    ClutterContent *image_new = nullptr;

    {
        /* the following must be done under lock in case a 'stopped' signal is
         * received during rendering; otherwise the mem could become invalid */
        std::lock_guard<std::mutex> lock(wg_renderer->run_mutex);

        if (!wg_renderer->running)
            return;

        auto renderer = wg_renderer->renderer;
        if (renderer == nullptr)
            return;

        const auto frameData = (const guint8*)renderer->currentFrame().constData();
        if (!frameData or !wg_renderer->dirty)
            return;

        wg_renderer->dirty = false;

        image_new = clutter_image_new();
        g_return_if_fail(image_new);

        const auto& res = renderer->size();
        const gint BPP = 4;
        const gint ROW_STRIDE = BPP * res.width();

        GError *error = nullptr;
        clutter_image_set_data(
            CLUTTER_IMAGE(image_new),
            frameData,
            COGL_PIXEL_FORMAT_BGRA_8888,
            res.width(),
            res.height(),
            ROW_STRIDE,
            &error);
        if (error) {
            g_warning("error rendering image to clutter: %s", error->message);
496
            g_clear_error(&error);
497 498 499
            g_object_unref (image_new);
            return;
        }
500 501
    }

Guillaume Roguez's avatar
Guillaume Roguez committed
502
    clutter_actor_set_content(actor, image_new);
503
    g_object_unref (image_new);
Guillaume Roguez's avatar
Guillaume Roguez committed
504

505 506 507
    /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
     * that the aspect ratio is correct
     */
Guillaume Roguez's avatar
Guillaume Roguez committed
508
    clutter_actor_set_content_gravity(actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
509 510 511 512 513 514 515 516
}

static gboolean
check_frame_queue(VideoWidget *self)
{
    g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

Guillaume Roguez's avatar
Guillaume Roguez committed
517 518 519
    /* display renderer's frames */
    clutter_render_image(priv->local);
    clutter_render_image(priv->remote);
520 521

    return TRUE; /* keep going */
522 523
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
524 525
static void
renderer_stop(VideoWidgetRenderer *renderer)
526
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
527
    QObject::disconnect(renderer->frame_update);
528 529 530 531 532 533
    {
        /* must do this under lock, in case the rendering is taking place when
         * this signal is received */
        std::lock_guard<std::mutex> lock(renderer->run_mutex);
        renderer->running = false;
    }
534 535
    /* ask to show a black frame */
    renderer->show_black_frame = true;
536 537
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
538 539
static void
renderer_start(VideoWidgetRenderer *renderer)
540
{
541
    QObject::disconnect(renderer->frame_update);
542 543 544 545
    {
        std::lock_guard<std::mutex> lock(renderer->run_mutex);
        renderer->running = true;
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
546 547
    renderer->frame_update = QObject::connect(
        renderer->renderer,
548
        &Video::Renderer::frameUpdated,
Guillaume Roguez's avatar
Guillaume Roguez committed
549 550 551
        [renderer]() {
            // WARNING: this lambda is called in LRC renderer thread,
            // but check_frame_queue() is in mainloop!
552 553 554 555

            /* make sure show_black_frame is false since it will take priority
             * over a new frame from the Video::Renderer */
            renderer->show_black_frame = false;
Guillaume Roguez's avatar
Guillaume Roguez committed
556
            renderer->dirty = true;
557
        }
Guillaume Roguez's avatar
Guillaume Roguez committed
558
        );
559 560
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
561
static void
562
free_video_widget_renderer(VideoWidgetRenderer *renderer)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
563 564 565 566
{
    QObject::disconnect(renderer->frame_update);
    QObject::disconnect(renderer->render_stop);
    QObject::disconnect(renderer->render_start);
567
    g_free(renderer);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
568
}
569

570
static void
571
video_widget_add_renderer(VideoWidget *self, VideoWidgetRenderer *new_video_renderer)
572 573
{
    g_return_if_fail(IS_VIDEO_WIDGET(self));
574 575
    g_return_if_fail(new_video_renderer);
    g_return_if_fail(new_video_renderer->renderer);
576 577 578 579

    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

    /* update the renderer */
580
    switch(new_video_renderer->type) {
581
        case VIDEO_RENDERER_REMOTE:
582 583 584 585 586 587 588 589
            /* swap the remote renderer */
            new_video_renderer->actor = priv->remote->actor;
            free_video_widget_renderer(priv->remote);
            priv->remote = new_video_renderer;
            /* reset the content gravity so that the aspect ratio gets properly
             * reset if it chagnes */
            clutter_actor_set_content_gravity(priv->remote->actor,
                                              CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
590 591
            break;
        case VIDEO_RENDERER_LOCAL:
592 593 594 595 596 597 598 599
            /* swap the remote renderer */
            new_video_renderer->actor = priv->local->actor;
            free_video_widget_renderer(priv->local);
            priv->local = new_video_renderer;
            /* reset the content gravity so that the aspect ratio gets properly
             * reset if it chagnes */
            clutter_actor_set_content_gravity(priv->local->actor,
                                              CLUTTER_CONTENT_GRAVITY_RESIZE_FILL);
600 601 602 603
            break;
        case VIDEO_RENDERER_COUNT:
            break;
    }
604
}
605 606 607 608 609 610 611 612

static gboolean
check_renderer_queue(VideoWidget *self)
{
    g_return_val_if_fail(IS_VIDEO_WIDGET(self), G_SOURCE_REMOVE);
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

    /* get all the renderers in the queue */
613
    VideoWidgetRenderer *new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
614
    while (new_video_renderer) {
615 616
        video_widget_add_renderer(self, new_video_renderer);
        new_video_renderer = (VideoWidgetRenderer *)g_async_queue_try_pop(priv->new_renderer_queue);
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
    }

    return G_SOURCE_CONTINUE;
}

/*
 * video_widget_new()
 *
 * The function use to create a new video_widget
 */
GtkWidget*
video_widget_new(void)
{
    GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
    return self;
}

/**
 * video_widget_push_new_renderer()
 *
 * This function is used add a new Video::Renderer to the VideoWidget in a
 * thread-safe manner.
 */
void
video_widget_push_new_renderer(VideoWidget *self, Video::Renderer *renderer, VideoRendererType type)
{
    g_return_if_fail(IS_VIDEO_WIDGET(self));
    VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);

646 647 648 649
    /* if the renderer is nullptr, there is nothing to be done */
    if (!renderer) return;

    VideoWidgetRenderer *new_video_renderer = g_new0(VideoWidgetRenderer, 1);
650 651 652
    new_video_renderer->renderer = renderer;
    new_video_renderer->type = type;

653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    if (new_video_renderer->renderer->isRendering())
        renderer_start(new_video_renderer);

    new_video_renderer->render_stop = QObject::connect(
        new_video_renderer->renderer,
        &Video::Renderer::stopped,
        [=]() {
            renderer_stop(new_video_renderer);
        }
    );

    new_video_renderer->render_start = QObject::connect(
        new_video_renderer->renderer,
        &Video::Renderer::started,
        [=]() {
            renderer_start(new_video_renderer);
        }
    );

672 673
    g_async_queue_push(priv->new_renderer_queue, new_video_renderer);
}