play_media.c (19385B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2022--2025 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your option) any later version. 9 10 GNUnet is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author Tobias Frisch 22 * @file ui/play_media.c 23 */ 24 25 #include "play_media.h" 26 27 #include "../application.h" 28 #include "../ui.h" 29 #include "../util.h" 30 #include <glib-2.0/glib.h> 31 32 gboolean 33 ui_play_media_window_supports_file_extension(const gchar *filename) 34 { 35 if (!filename) 36 return FALSE; 37 38 const char* extension = strrchr(filename, '.'); 39 40 if (!extension) 41 return FALSE; 42 43 if (0 == g_strcmp0(extension, ".mkv")) 44 return TRUE; 45 if (0 == g_strcmp0(extension, ".mp4")) 46 return TRUE; 47 if (0 == g_strcmp0(extension, ".webm")) 48 return TRUE; 49 50 return FALSE; 51 } 52 53 static void 54 handle_back_button_click(UNUSED GtkButton *button, 55 gpointer user_data) 56 { 57 g_assert(user_data); 58 59 GtkWindow *window = GTK_WINDOW(user_data); 60 gtk_window_close(window); 61 } 62 63 static void 64 _set_media_controls_sensivity(UI_PLAY_MEDIA_Handle *handle, 65 gboolean sensitive) 66 { 67 g_assert(handle); 68 69 if (handle->play_pause_button) 70 gtk_widget_set_sensitive( 71 GTK_WIDGET(handle->play_pause_button), 72 sensitive 73 ); 74 75 if (handle->volume_button) 76 gtk_widget_set_sensitive( 77 GTK_WIDGET(handle->volume_button), 78 sensitive 79 ); 80 81 if (handle->timeline_scale) 82 gtk_widget_set_sensitive( 83 GTK_WIDGET(handle->timeline_scale), 84 sensitive 85 ); 86 } 87 88 static void 89 handle_timeline_scale_value_changed(GtkRange *range, 90 gpointer user_data); 91 92 static void 93 _set_signal_connection_of_timeline(UI_PLAY_MEDIA_Handle *handle, 94 gboolean connected) 95 { 96 g_assert(handle); 97 98 if (!(handle->timeline_scale)) 99 return; 100 101 if (connected == (handle->timeline_signal != 0)) 102 return; 103 104 if (connected) 105 handle->timeline_signal = g_signal_connect( 106 handle->timeline_scale, 107 "value-changed", 108 G_CALLBACK(handle_timeline_scale_value_changed), 109 handle 110 ); 111 else 112 { 113 g_signal_handler_disconnect( 114 handle->timeline_scale, 115 handle->timeline_signal 116 ); 117 118 handle->timeline_signal = 0; 119 } 120 } 121 122 static void 123 _set_media_position(UI_PLAY_MEDIA_Handle *handle, 124 gint64 pos, 125 gint64 len, 126 gboolean include_scale) 127 { 128 g_assert(handle); 129 130 const gdouble position = ( 131 len > 0? 1.0 * pos / len : 0.0 132 ); 133 134 if (handle->timeline_label) 135 { 136 GString *str = g_string_new(NULL); 137 138 guint pos_seconds = GST_TIME_AS_SECONDS(pos); 139 guint len_seconds = GST_TIME_AS_SECONDS(len); 140 141 g_string_append_printf( 142 str, 143 "%u:%02u / %u:%02u", 144 pos_seconds / 60, 145 pos_seconds % 60, 146 len_seconds / 60, 147 len_seconds % 60 148 ); 149 150 ui_label_set_text(handle->timeline_label, str->str); 151 g_string_free(str, TRUE); 152 } 153 154 if (handle->timeline_progress_bar) 155 gtk_progress_bar_set_fraction( 156 handle->timeline_progress_bar, 157 1.0 * position 158 ); 159 160 if ((!(handle->timeline_scale)) || (!include_scale)) 161 return; 162 163 _set_signal_connection_of_timeline(handle, FALSE); 164 165 gtk_range_set_value( 166 GTK_RANGE(handle->timeline_scale), 167 100.0 * position 168 ); 169 170 _set_signal_connection_of_timeline(handle, TRUE); 171 } 172 173 static gboolean 174 _adjust_playing_media_position(UI_PLAY_MEDIA_Handle *handle); 175 176 static void 177 _set_next_timeout_callback_of_timeline(UI_PLAY_MEDIA_Handle *handle) 178 { 179 g_assert(handle); 180 181 handle->timeline = util_timeout_add( 182 200, 183 G_SOURCE_FUNC(_adjust_playing_media_position), 184 handle 185 ); 186 } 187 188 static gboolean 189 _adjust_playing_media_position(UI_PLAY_MEDIA_Handle *handle) 190 { 191 g_assert(handle); 192 193 gint64 pos, len; 194 195 handle->timeline = 0; 196 197 if (!(handle->pipeline)) 198 return G_SOURCE_REMOVE; 199 200 if (!gst_element_query_position(handle->pipeline, GST_FORMAT_TIME, &pos)) 201 return G_SOURCE_REMOVE; 202 203 if (!gst_element_query_duration(handle->pipeline, GST_FORMAT_TIME, &len)) 204 return G_SOURCE_REMOVE; 205 206 _set_media_position(handle, pos, len, TRUE); 207 _set_next_timeout_callback_of_timeline(handle); 208 return G_SOURCE_REMOVE; 209 } 210 211 static void 212 _set_timeout_callback_of_timeline(UI_PLAY_MEDIA_Handle *handle, 213 gboolean connected) 214 { 215 g_assert(handle); 216 217 if (handle->timeline) 218 util_source_remove(handle->timeline); 219 220 if (connected) 221 _set_next_timeout_callback_of_timeline(handle); 222 else 223 handle->timeline = 0; 224 } 225 226 static void 227 _set_media_state(UI_PLAY_MEDIA_Handle *handle, 228 gboolean playing) 229 { 230 g_assert(handle); 231 232 if (handle->play_symbol_stack) 233 gtk_stack_set_visible_child_name( 234 handle->play_symbol_stack, 235 playing? "pause_page" : "play_page" 236 ); 237 238 _set_timeout_callback_of_timeline(handle, playing); 239 } 240 241 static void 242 _disable_video_processing(UI_PLAY_MEDIA_Handle *handle, 243 gboolean drop_pipeline) 244 { 245 g_assert(handle); 246 247 if (handle->preview_stack) 248 gtk_stack_set_visible_child(handle->preview_stack, handle->fail_box); 249 250 _set_media_controls_sensivity(handle, FALSE); 251 _set_media_position(handle, 0, 0, TRUE); 252 _set_media_state(handle, FALSE); 253 254 if ((!(handle->pipeline)) || (!drop_pipeline)) 255 return; 256 257 gst_element_set_state(handle->pipeline, GST_STATE_NULL); 258 } 259 260 static void 261 _adjust_playing_media_state(UI_PLAY_MEDIA_Handle *handle, gboolean playing) 262 { 263 _set_media_state(handle, playing); 264 265 if (handle->preview_stack) 266 gtk_stack_set_visible_child( 267 handle->preview_stack, 268 handle->video_box 269 ); 270 } 271 272 static void 273 _pause_playing_media(UI_PLAY_MEDIA_Handle *handle) 274 { 275 if (!(handle->pipeline)) 276 return; 277 278 GstStateChangeReturn ret = gst_element_set_state( 279 handle->pipeline, 280 GST_STATE_PAUSED 281 ); 282 283 if (GST_STATE_CHANGE_FAILURE == ret) 284 { 285 _disable_video_processing(handle, TRUE); 286 return; 287 } 288 } 289 290 static void 291 _continue_playing_media(UI_PLAY_MEDIA_Handle *handle) 292 { 293 if (!(handle->pipeline)) 294 return; 295 296 GstStateChangeReturn ret = gst_element_set_state( 297 handle->pipeline, 298 GST_STATE_PLAYING 299 ); 300 301 if (GST_STATE_CHANGE_FAILURE == ret) 302 { 303 _disable_video_processing(handle, TRUE); 304 return; 305 } 306 } 307 308 static void 309 handle_play_pause_button_click(GtkButton *button, 310 gpointer user_data) 311 { 312 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 313 314 if (!(handle->play_symbol_stack)) 315 return; 316 317 const gchar *page = gtk_stack_get_visible_child_name( 318 handle->play_symbol_stack 319 ); 320 321 if (0 == g_strcmp0(page, "pause_page")) 322 _pause_playing_media(handle); 323 else 324 _continue_playing_media(handle); 325 } 326 327 static void 328 handle_volume_button_value_changed(GtkScaleButton *button, 329 double value, 330 gpointer user_data) 331 { 332 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 333 334 if (!(handle->pipeline)) 335 return; 336 337 g_object_set( 338 G_OBJECT(handle->pipeline), 339 "volume", 340 value, 341 NULL 342 ); 343 344 g_object_set( 345 G_OBJECT(handle->pipeline), 346 "mute", 347 (value <= 0.0), 348 NULL 349 ); 350 } 351 352 static void 353 handle_timeline_scale_value_changed(GtkRange *range, 354 gpointer user_data) 355 { 356 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 357 gint64 pos, len; 358 359 if (!(handle->pipeline)) 360 return; 361 362 if (!gst_element_query_duration(handle->pipeline, GST_FORMAT_TIME, &len)) 363 return; 364 365 pos = (gint64) (gtk_range_get_value(range) * len / 100); 366 367 if (gst_element_seek_simple(handle->pipeline, 368 GST_FORMAT_TIME, 369 GST_SEEK_FLAG_FLUSH, 370 pos)) 371 _set_media_position(handle, pos, len, FALSE); 372 } 373 374 static void 375 handle_fullscreen_button_click(GtkButton *button, 376 gpointer user_data) 377 { 378 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 379 380 gtk_revealer_set_reveal_child(handle->header_revealer, handle->fullscreen); 381 hdy_flap_set_reveal_flap(handle->controls_flap, handle->fullscreen); 382 383 handle->fullscreen = !(handle->fullscreen); 384 385 if (!(handle->fullscreen)) 386 gtk_window_unfullscreen(GTK_WINDOW(handle->window)); 387 388 gtk_widget_hide(GTK_WIDGET(handle->window)); 389 390 gtk_window_set_type_hint( 391 GTK_WINDOW(handle->window), 392 handle->fullscreen? 393 GDK_WINDOW_TYPE_HINT_NORMAL : 394 GDK_WINDOW_TYPE_HINT_DIALOG 395 ); 396 397 gtk_window_set_modal(GTK_WINDOW(handle->window), !(handle->fullscreen)); 398 399 gtk_window_set_position( 400 GTK_WINDOW(handle->window), 401 handle->fullscreen? GTK_WIN_POS_NONE : GTK_WIN_POS_CENTER_ON_PARENT 402 ); 403 404 gtk_window_set_transient_for( 405 GTK_WINDOW(handle->window), 406 handle->fullscreen? NULL : handle->parent 407 ); 408 409 gtk_widget_show_all(GTK_WIDGET(handle->window)); 410 hdy_flap_set_reveal_flap(handle->controls_flap, !(handle->fullscreen)); 411 412 if (handle->fullscreen) 413 gtk_window_fullscreen(GTK_WINDOW(handle->window)); 414 else 415 { 416 if (handle->motion_lost) 417 util_source_remove(handle->motion_lost); 418 419 handle->motion_lost = 0; 420 } 421 422 gtk_stack_set_visible_child_name( 423 handle->fullscreen_symbol_stack, 424 handle->fullscreen? "scale_down_page" : "scale_up_page" 425 ); 426 } 427 428 static gboolean 429 handle_media_motion_lost(gpointer user_data) 430 { 431 g_assert(user_data); 432 433 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 434 435 handle->motion_lost = 0; 436 437 if (!(hdy_flap_get_reveal_flap(handle->controls_flap))) 438 return G_SOURCE_REMOVE; 439 440 hdy_flap_set_reveal_flap(handle->controls_flap, FALSE); 441 return G_SOURCE_REMOVE; 442 } 443 444 static gboolean 445 handle_media_motion_notify(GtkWidget *widget, 446 GdkEvent *event, 447 gpointer user_data) 448 { 449 g_assert(user_data); 450 451 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; 452 453 if (hdy_flap_get_reveal_flap(handle->controls_flap)) 454 return G_SOURCE_REMOVE; 455 456 if (handle->motion_lost) 457 util_source_remove(handle->motion_lost); 458 459 hdy_flap_set_reveal_flap(handle->controls_flap, TRUE); 460 461 if (!(handle->fullscreen)) 462 return G_SOURCE_REMOVE; 463 464 handle->motion_lost = util_timeout_add_seconds( 465 3, 466 G_SOURCE_FUNC(handle_media_motion_lost), 467 handle 468 ); 469 470 return G_SOURCE_REMOVE; 471 } 472 473 static void 474 handle_window_destroy(UNUSED GtkWidget *window, 475 gpointer user_data) 476 { 477 g_assert(user_data); 478 479 ui_play_media_window_cleanup((UI_PLAY_MEDIA_Handle*) user_data); 480 } 481 482 static void 483 msg_error_cb(UNUSED GstBus *bus, 484 GstMessage *msg, 485 gpointer data) 486 { 487 g_assert((msg) && (data)); 488 489 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; 490 491 GError* error; 492 gst_message_parse_error(msg, &error, NULL); 493 494 if (!error) 495 fprintf(stderr, "ERROR: Unknown error\n"); 496 else if (error->message) 497 fprintf(stderr, "ERROR: %s (%d)\n", error->message, error->code); 498 else 499 fprintf(stderr, "ERROR: Unknown error (%d)\n", error->code); 500 501 _disable_video_processing(handle, TRUE); 502 } 503 504 static void 505 msg_eos_cb(UNUSED GstBus *bus, 506 UNUSED GstMessage *msg, 507 gpointer data) 508 { 509 g_assert(data); 510 511 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; 512 513 if (GST_MESSAGE_SRC(msg) != GST_OBJECT(handle->pipeline)) 514 return; 515 516 if (handle->timeline_scale) 517 gtk_range_set_value(GTK_RANGE(handle->timeline_scale), 0.0); 518 519 _adjust_playing_media_state(handle, FALSE); 520 } 521 522 static void 523 msg_state_changed_cb(UNUSED GstBus *bus, 524 GstMessage *msg, 525 gpointer data) 526 { 527 g_assert((msg) && (data)); 528 529 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; 530 531 GstState old_state, new_state, pending_state; 532 gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); 533 534 if (GST_MESSAGE_SRC(msg) != GST_OBJECT(handle->pipeline)) 535 return; 536 537 if (!(handle->sink)) 538 { 539 _disable_video_processing(handle, FALSE); 540 return; 541 } 542 543 if (GST_STATE_READY == new_state) 544 _set_media_controls_sensivity(handle, TRUE); 545 546 if ((GST_STATE_PLAYING != new_state) && (GST_STATE_PAUSED != new_state)) 547 return; 548 549 _adjust_playing_media_state(handle, GST_STATE_PLAYING == new_state); 550 } 551 552 static void 553 msg_buffering_cb(UNUSED GstBus *bus, 554 GstMessage *msg, 555 gpointer data) 556 { 557 g_assert((msg) && (data)); 558 559 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; 560 561 gint percent = 0; 562 gst_message_parse_buffering(msg, &percent); 563 564 GstStateChangeReturn ret = gst_element_set_state( 565 handle->pipeline, 566 (percent < 100? GST_STATE_PAUSED : GST_STATE_PLAYING) 567 ); 568 569 if (ret == GST_STATE_CHANGE_FAILURE) 570 _disable_video_processing(handle, TRUE); 571 } 572 573 static void 574 _setup_gst_pipeline(UI_PLAY_MEDIA_Handle *handle) 575 { 576 g_assert(handle); 577 578 handle->pipeline = gst_element_factory_make("playbin", NULL); 579 580 if (!(handle->pipeline)) 581 return; 582 583 handle->sink = gst_element_factory_make("gtksink", "vsink"); 584 585 if (!(handle->sink)) 586 return; 587 588 g_object_set( 589 G_OBJECT(handle->pipeline), 590 "video-sink", 591 handle->sink, 592 NULL 593 ); 594 595 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->pipeline)); 596 597 if (!bus) 598 return; 599 600 gst_bus_add_signal_watch(bus); 601 602 g_signal_connect( 603 G_OBJECT(bus), 604 "message::error", 605 (GCallback) msg_error_cb, 606 handle 607 ); 608 609 g_signal_connect( 610 G_OBJECT(bus), 611 "message::eos", 612 (GCallback) msg_eos_cb, 613 handle 614 ); 615 616 g_signal_connect( 617 G_OBJECT(bus), 618 "message::state-changed", 619 (GCallback) msg_state_changed_cb, 620 handle 621 ); 622 623 g_signal_connect( 624 G_OBJECT(bus), 625 "message::buffering", 626 (GCallback) msg_buffering_cb, 627 handle 628 ); 629 630 gst_object_unref(bus); 631 } 632 633 static void* 634 _ui_play_media_video_thread(void *args) 635 { 636 g_assert(args); 637 638 UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) args; 639 _continue_playing_media(handle); 640 return NULL; 641 } 642 643 void 644 ui_play_media_window_init(MESSENGER_Application *app, 645 UI_PLAY_MEDIA_Handle *handle) 646 { 647 g_assert((app) && (handle)); 648 649 _setup_gst_pipeline(handle); 650 651 handle->parent = GTK_WINDOW(app->ui.messenger.main_window); 652 653 handle->builder = ui_builder_from_resource( 654 application_get_resource_path(app, "ui/play_media.ui") 655 ); 656 657 handle->window = HDY_WINDOW( 658 gtk_builder_get_object(handle->builder, "play_media_window") 659 ); 660 661 gtk_window_set_position( 662 GTK_WINDOW(handle->window), 663 GTK_WIN_POS_CENTER_ON_PARENT 664 ); 665 666 gtk_window_set_transient_for( 667 GTK_WINDOW(handle->window), 668 handle->parent 669 ); 670 671 handle->header_revealer = GTK_REVEALER( 672 gtk_builder_get_object(handle->builder, "header_revealer") 673 ); 674 675 handle->title_bar = HDY_HEADER_BAR( 676 gtk_builder_get_object(handle->builder, "title_bar") 677 ); 678 679 handle->back_button = GTK_BUTTON( 680 gtk_builder_get_object(handle->builder, "back_button") 681 ); 682 683 g_signal_connect( 684 handle->back_button, 685 "clicked", 686 G_CALLBACK(handle_back_button_click), 687 handle->window 688 ); 689 690 handle->controls_flap = HDY_FLAP( 691 gtk_builder_get_object(handle->builder, "controls_flap") 692 ); 693 694 handle->preview_stack = GTK_STACK( 695 gtk_builder_get_object(handle->builder, "preview_stack") 696 ); 697 698 handle->fail_box = GTK_WIDGET( 699 gtk_builder_get_object(handle->builder, "fail_box") 700 ); 701 702 handle->video_box = GTK_WIDGET( 703 gtk_builder_get_object(handle->builder, "video_box") 704 ); 705 706 GtkWidget *widget; 707 if (handle->sink) 708 g_object_get(handle->sink, "widget", &widget, NULL); 709 else 710 widget = NULL; 711 712 if (widget) 713 { 714 gtk_box_pack_start( 715 GTK_BOX(handle->video_box), 716 widget, 717 true, 718 true, 719 0 720 ); 721 722 g_object_unref(widget); 723 gtk_widget_realize(widget); 724 725 gtk_widget_show_all(handle->video_box); 726 } 727 728 handle->play_pause_button = GTK_BUTTON( 729 gtk_builder_get_object(handle->builder, "play_pause_button") 730 ); 731 732 handle->play_symbol_stack = GTK_STACK( 733 gtk_builder_get_object(handle->builder, "play_symbol_stack") 734 ); 735 736 g_signal_connect( 737 handle->play_pause_button, 738 "clicked", 739 G_CALLBACK(handle_play_pause_button_click), 740 handle 741 ); 742 743 handle->volume_button = GTK_VOLUME_BUTTON( 744 gtk_builder_get_object(handle->builder, "volume_button") 745 ); 746 747 g_signal_connect( 748 handle->volume_button, 749 "value-changed", 750 G_CALLBACK(handle_volume_button_value_changed), 751 handle 752 ); 753 754 handle->timeline_label = GTK_LABEL( 755 gtk_builder_get_object(handle->builder, "timeline_label") 756 ); 757 758 handle->timeline_progress_bar = GTK_PROGRESS_BAR( 759 gtk_builder_get_object(handle->builder, "timeline_progress_bar") 760 ); 761 762 handle->timeline_scale = GTK_SCALE( 763 gtk_builder_get_object(handle->builder, "timeline_scale") 764 ); 765 766 _set_signal_connection_of_timeline(handle, handle->sink? TRUE : FALSE); 767 768 handle->settings_button = GTK_BUTTON( 769 gtk_builder_get_object(handle->builder, "settings_button") 770 ); 771 772 handle->fullscreen_button = GTK_BUTTON( 773 gtk_builder_get_object(handle->builder, "fullscreen_button") 774 ); 775 776 handle->fullscreen_symbol_stack = GTK_STACK( 777 gtk_builder_get_object(handle->builder, "fullscreen_symbol_stack") 778 ); 779 780 g_signal_connect( 781 handle->fullscreen_button, 782 "clicked", 783 G_CALLBACK(handle_fullscreen_button_click), 784 handle 785 ); 786 787 g_signal_connect( 788 handle->window, 789 "motion-notify-event", 790 G_CALLBACK(handle_media_motion_notify), 791 handle 792 ); 793 794 gtk_widget_add_events( 795 GTK_WIDGET(handle->window), 796 GDK_POINTER_MOTION_HINT_MASK | 797 GDK_POINTER_MOTION_MASK 798 ); 799 800 g_signal_connect( 801 handle->window, 802 "destroy", 803 G_CALLBACK(handle_window_destroy), 804 handle 805 ); 806 807 gtk_scale_button_set_value( 808 GTK_SCALE_BUTTON(handle->volume_button), 809 1.0 810 ); 811 812 gtk_widget_show_all(GTK_WIDGET(handle->window)); 813 } 814 815 void 816 ui_play_media_window_update(UI_PLAY_MEDIA_Handle *handle, 817 const gchar *uri, 818 const struct GNUNET_CHAT_File *file) 819 { 820 g_assert((handle) && (uri)); 821 822 if (handle->video_tid) 823 pthread_join(handle->video_tid, NULL); 824 825 if (!(handle->pipeline)) 826 return; 827 828 _disable_video_processing(handle, TRUE); 829 g_object_set(G_OBJECT(handle->pipeline), "uri", uri, NULL); 830 831 const gchar *filename; 832 833 if (file) 834 filename = GNUNET_CHAT_file_get_name(file); 835 else 836 filename = uri; 837 838 hdy_header_bar_set_subtitle( 839 handle->title_bar, 840 filename? filename : "" 841 ); 842 843 pthread_create( 844 &(handle->video_tid), 845 NULL, 846 _ui_play_media_video_thread, 847 handle 848 ); 849 } 850 851 void 852 ui_play_media_window_cleanup(UI_PLAY_MEDIA_Handle *handle) 853 { 854 g_assert(handle); 855 856 if (handle->video_tid) 857 pthread_join(handle->video_tid, NULL); 858 859 g_object_unref(handle->builder); 860 861 if (handle->timeline) 862 util_source_remove(handle->timeline); 863 864 if (handle->motion_lost) 865 util_source_remove(handle->motion_lost); 866 867 if (handle->pipeline) 868 { 869 gst_element_set_state(handle->pipeline, GST_STATE_NULL); 870 gst_object_unref(GST_OBJECT(handle->pipeline)); 871 } 872 873 memset(handle, 0, sizeof(*handle)); 874 }