From 39a034aff24b2b64b028345aeae66c7aae6f8820 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 18 Nov 2021 18:10:45 +0800 Subject: [PATCH] Implement a media xwidget based on GStreamer * configure.ac: Add option to use GStreamer for media playback. * doc/lispref/display.texi (Xwidgets): Document new functions and xwidget type. * src/Makefile.in (EMACS_CFLAGS): Add GStreamer cflags. (LIBES): Add GStreamer linker options. * etc/NEWS: Announce `media' xwidget. * etc/xterm.c (x_scroll_run): Don't resize cairo surface if not a GTK xwidget. (x_term_init): Initialize GStreamer. * src/xwidget.c (gst_create_window) (gst_message_cb, unlink_gst_xwidget_view) (check_gstreamer_dependencies): New functions. (Fmake_xwidget): Add support for media xwidgets. (Fxwidget_perform_lispy_event): Return nil if it doesn't make sense to perform an event. (Fxwidget_resize): Add support for media widgets. (xwidget_button, xwidget_motion_or_crossing): Handle non-GTK xwidgets. (xwidget_expose): Handle media xwidgets. (x_draw_xwidget_glyph_string): Set up GStreamer widgets if appropriate. (Fxwidget_size_request): Return nil on widgets that can't have a size request. (Fdelete_xwidget_view): Clean up after GStreamer. (Fxwidget_media_pause, Fxwidget_media_play): New functions. (syms_of_xwidget): New symbols and subrs. (kill_xwidget): Clean up after GStreamer. * src/xwidget.h (struct xwidget, struct xwidget_view): Add GStreamer specific fields for media xwidgets. (XWIDGET_VIEW_GTK_P): New macro. --- configure.ac | 13 + doc/lispref/display.texi | 12 + etc/NEWS | 7 + src/Makefile.in | 7 +- src/xterm.c | 16 +- src/xwidget.c | 536 +++++++++++++++++++++++++++++++-------- src/xwidget.h | 25 ++ 7 files changed, 508 insertions(+), 108 deletions(-) diff --git a/configure.ac b/configure.ac index c231c2ceae..ae3f28f8db 100644 --- a/configure.ac +++ b/configure.ac @@ -485,6 +485,7 @@ AC_DEFUN OPTION_DEFAULT_ON([zlib],[don't compile with zlib decompression support]) OPTION_DEFAULT_ON([modules],[don't compile with dynamic modules support]) OPTION_DEFAULT_ON([threads],[don't compile with elisp threading support]) +OPTION_DEFAULT_OFF([gstreamer],[compile with xwidget video playback support using GStreamer]) OPTION_DEFAULT_OFF([native-compilation],[compile with Emacs Lisp native compiler support]) OPTION_DEFAULT_OFF([cygwin32-native-compilation],[use native compilation on 32-bit Cygwin]) @@ -2834,6 +2835,18 @@ AC_DEFUN fi AC_SUBST(XWIDGETS_OBJ) +HAVE_GSTREAMER=no +if test "$with_gstreamer" != "no" && test "${HAVE_X_WINDOWS}" = "yes" \ + && test "${HAVE_XWIDGETS}" = "yes"; then + EMACS_CHECK_MODULES([GSTREAMER], "gstreamer-1.0 gstreamer-video-1.0") + if test "$HAVE_GSTREAMER" = "yes"; then + AC_DEFINE(HAVE_GSTREAMER, 1, [Define to 1 if using GStreamer for video support in xwidgets.]) + fi +fi + +AC_SUBST(GSTREAMER_CFLAGS) +AC_SUBST(GSTREAMER_LIBS) + CFLAGS=$OLD_CFLAGS LIBS=$OLD_LIBS diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index dd2c6e003f..1b4a3d9a22 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -6797,6 +6797,8 @@ Xwidgets @table @code @item webkit The WebKit component. +@item media +A media widget that allows to display videos inside Emacs buffers. @end table The @var{width} and @var{height} arguments specify the widget size in @@ -7004,6 +7006,16 @@ Xwidgets be passed as an index to @code{xwidget-webkit-goto-history}. @end defun +@defun xwidget-media-pause xwidget +Suspend playback of @var{xwidget}, a media xwidget. You can later +resume playback using @code{xwidget-media-play}. +@end defun + +@defun xwidget-media-pause xwidget +Start or resume playback of @var{xwidget}, a media xwidget. You can +later pause playback using @code{xwidget-media-pause}. +@end defun + @node Buttons @section Buttons @cindex buttons in buffers diff --git a/etc/NEWS b/etc/NEWS index cee2844be3..8cb5495b4e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -488,6 +488,13 @@ This is a convenience function to extract the field data from ** Xwidgets ++++ +*** New xwidget type 'media'. +This xwidget type allows to display video content inside Emacs buffers. +You must have the GStreamer library installed, along with the plugins +"xvimagesink", "queue", "videotestsrc" and "tee", and Emacs must be +built with '--with-gstreamer'. + --- *** New user option 'xwidget-webkit-buffer-name-format'. Using this option you can control how the xwidget-webkit buffers are diff --git a/src/Makefile.in b/src/Makefile.in index 4c5535f8ad..646392c051 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -333,6 +333,9 @@ LIBGMP = LIBGCCJIT_LIBS = @LIBGCCJIT_LIBS@ LIBGCCJIT_CFLAGS = @LIBGCCJIT_CFLAGS@ +GSTREAMER_LIBS = @GSTREAMER_LIBS@ +GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@ + ## dynlib.o if necessary, else empty DYNLIB_OBJ = @DYNLIB_OBJ@ @@ -377,7 +380,7 @@ EMACS_CFLAGS= $(WEBKIT_CFLAGS) $(WEBP_CFLAGS) $(LCMS2_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ - $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) $(GSTREAMER_CFLAGS) \ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WERROR_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) @@ -524,7 +527,7 @@ LIBES = $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \ $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \ - $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) + $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(GSTREAMER_LIBS) ## FORCE it so that admin/unidata can decide whether this file is ## up-to-date. Although since charprop depends on bootstrap-emacs, diff --git a/src/xterm.c b/src/xterm.c index 816b6dc5a8..a7116d9d95 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -4471,9 +4471,11 @@ x_scroll_run (struct window *w, struct run *run) view->y + view->clip_top, view->clip_right - view->clip_left, view->clip_bottom - view->clip_top); - cairo_xlib_surface_set_size (view->cr_surface, - view->clip_right - view->clip_left, - view->clip_bottom - view->clip_top); + + if (XWIDGET_VIEW_GTK_P (view)) + cairo_xlib_surface_set_size (view->cr_surface, + view->clip_right - view->clip_left, + view->clip_bottom - view->clip_top); } xwidget_expose (view); XFlush (dpy); @@ -12940,6 +12942,14 @@ #define NUM_ARGV 10 gtk_init (&argc, &argv2); request_sigio (); +#ifdef HAVE_GSTREAMER + unrequest_sigio (); + /* gst_init might call XOpenDisplay, which fails if a signal + is received. */ + gst_init (&argc, &argv2); + request_sigio (); +#endif + g_log_remove_handler ("GLib", id); xg_initialize (); diff --git a/src/xwidget.c b/src/xwidget.c index e1bf40ea43..ffed743412 100644 --- a/src/xwidget.c +++ b/src/xwidget.c @@ -40,6 +40,10 @@ Copyright (C) 2011-2021 Free Software Foundation, Inc. #include #include #include +#ifdef HAVE_GSTREAMER +#include +#define XG_WINDOW_HANDLE "xg-window-handle" +#endif #elif defined NS_IMPL_COCOA #include "nsxwidget.h" #endif @@ -111,6 +115,12 @@ webkit_decide_policy_cb (WebKitWebView *, static void find_widget (GtkWidget *t, struct widget_search_data *); static void mouse_target_changed (WebKitWebView *, WebKitHitTestResult *, guint, gpointer); +#ifdef HAVE_GSTREAMER +static GstBusSyncReply gst_create_window (GstBus *, GstMessage *, gpointer); +static gboolean gst_message_cb (GstBus *, GstMessage *, gpointer); +static bool check_gstreamer_dependencies (void); +static void unlink_gst_xwidget_view (struct xwidget_view *); +#endif #endif @@ -123,6 +133,7 @@ DEFUN ("make-xwidget", TYPE is a symbol which can take one of the following values: - webkit +- media RELATED is nil, or an xwidget. When constructing a WebKit widget, it will share the same settings and internal subprocess as RELATED. @@ -140,9 +151,18 @@ DEFUN ("make-xwidget", CHECK_FIXNAT (width); CHECK_FIXNAT (height); - if (!EQ (type, Qwebkit)) + if (!EQ (type, Qwebkit) && !EQ (type, Qmedia)) error ("Bad xwidget type"); +#ifdef HAVE_GSTREAMER + if (EQ (type, Qmedia) + && !check_gstreamer_dependencies ()) + error ("Your Emacs is missing run-time dependencies required for GStreamer support"); +#else + if (EQ (type, Qmedia)) + error ("Your Emacs was not built with GStreamer support"); +#endif + struct xwidget *xw = allocate_xwidget (); Lisp_Object val; xw->type = type; @@ -180,103 +200,142 @@ DEFUN ("make-xwidget", gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width, xw->height); - if (EQ (xw->type, Qwebkit)) - { - WebKitWebView *related_view; + xw->xg_p = true; + WebKitWebView *related_view; + + if (NILP (related) + || !XWIDGETP (related) + || !EQ (XXWIDGET (related)->type, Qwebkit)) + { + xw->widget_osr = webkit_web_view_new (); + + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), + "about:blank"); + /* webkitgtk uses GSubprocess which sets sigaction causing + Emacs to not catch SIGCHLD with its usual handle setup in + 'catch_child_signal'. This resets the SIGCHLD sigaction. */ + catch_child_signal (); + } + else + { + related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr); + xw->widget_osr = webkit_web_view_new_with_related_view (related_view); + } + + /* Enable the developer extras. */ + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr)); + g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL); + + if (xw->xg_p) + { + gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width, + xw->height); - if (NILP (related) - || !XWIDGETP (related) - || !EQ (XXWIDGET (related)->type, Qwebkit)) + if (EQ (xw->type, Qwebkit)) { - xw->widget_osr = webkit_web_view_new (); - - webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), - "about:blank"); - /* webkitgtk uses GSubprocess which sets sigaction causing - Emacs to not catch SIGCHLD with its usual handle setup in - 'catch_child_signal'. This resets the SIGCHLD sigaction. */ - catch_child_signal (); + gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr), + GTK_WIDGET (WEBKIT_WEB_VIEW (xw->widget_osr))); } else { - related_view = WEBKIT_WEB_VIEW (XXWIDGET (related)->widget_osr); - xw->widget_osr = webkit_web_view_new_with_related_view (related_view); + gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr), + xw->widget_osr); } - /* Enable the developer extras. */ - settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (xw->widget_osr)); - g_object_set (G_OBJECT (settings), "enable-developer-extras", TRUE, NULL); + gtk_widget_show (xw->widget_osr); + gtk_widget_show (xw->widgetwindow_osr); + synthesize_focus_in_event (xw->widgetwindow_osr); + + + g_signal_connect (G_OBJECT (gtk_widget_get_window (xw->widgetwindow_osr)), + "from-embedder", G_CALLBACK (from_embedder), NULL); + g_signal_connect (G_OBJECT (gtk_widget_get_window (xw->widgetwindow_osr)), + "to-embedder", G_CALLBACK (to_embedder), NULL); + + /* Store some xwidget data in the gtk widgets for convenient + retrieval in the event handlers. */ + g_object_set_data (G_OBJECT (xw->widget_osr), XG_XWIDGET, xw); + g_object_set_data (G_OBJECT (xw->widgetwindow_osr), XG_XWIDGET, xw); + + /* signals */ + if (EQ (xw->type, Qwebkit)) + { + g_signal_connect (G_OBJECT (xw->widget_osr), + "load-changed", + G_CALLBACK (webkit_view_load_changed_cb), xw); + + g_signal_connect (G_OBJECT (webkit_context), + "download-started", + G_CALLBACK (webkit_download_cb), xw); + + g_signal_connect (G_OBJECT (xw->widget_osr), + "decide-policy", + G_CALLBACK + (webkit_decide_policy_cb), + xw); + + g_signal_connect (G_OBJECT (xw->widget_osr), + "mouse-target-changed", + G_CALLBACK (mouse_target_changed), + xw); + g_signal_connect (G_OBJECT (xw->widget_osr), + "create", + G_CALLBACK (webkit_create_cb), + xw); + g_signal_connect (G_OBJECT (xw->widget_osr), + "script-dialog", + G_CALLBACK (webkit_script_dialog_cb), + NULL); + g_signal_connect (G_OBJECT (xw->widget_osr), + "run-file-chooser", + G_CALLBACK (run_file_chooser_cb), + NULL); + } + + g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event", + G_CALLBACK (offscreen_damage_event), xw); } - gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width, - xw->height); + unblock_input (); + } +#ifdef HAVE_GSTREAMER + else + { + xw->xg_p = false; + xw->gst_pipeline = gst_pipeline_new (NULL); + xw->gst_source = gst_element_factory_make ("videotestsrc", NULL); + xw->gst_tee = gst_element_factory_make ("tee", NULL); - if (EQ (xw->type, Qwebkit)) - { - gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr), - GTK_WIDGET (WEBKIT_WEB_VIEW (xw->widget_osr))); - } - else - { - gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr), - xw->widget_osr); - } + GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (xw->gst_pipeline)); + GstElement *fakesink = gst_element_factory_make ("fakesink", NULL); + GstElement *queue = gst_element_factory_make ("queue", NULL); - gtk_widget_show (xw->widget_osr); - gtk_widget_show (xw->widgetwindow_osr); - synthesize_focus_in_event (xw->widgetwindow_osr); + g_object_set (G_OBJECT (fakesink), "sync", TRUE, NULL); + gst_bus_set_sync_handler (bus, gst_create_window, xw->gst_pipeline, NULL); + gst_bus_add_signal_watch (bus); + g_signal_connect (G_OBJECT (bus), "message", + G_CALLBACK (gst_message_cb), xw->gst_pipeline); - g_signal_connect (G_OBJECT (gtk_widget_get_window (xw->widgetwindow_osr)), - "from-embedder", G_CALLBACK (from_embedder), NULL); - g_signal_connect (G_OBJECT (gtk_widget_get_window (xw->widgetwindow_osr)), - "to-embedder", G_CALLBACK (to_embedder), NULL); + xw->gst_fakesink = fakesink; - /* Store some xwidget data in the gtk widgets for convenient - retrieval in the event handlers. */ - g_object_set_data (G_OBJECT (xw->widget_osr), XG_XWIDGET, xw); - g_object_set_data (G_OBJECT (xw->widgetwindow_osr), XG_XWIDGET, xw); + gst_object_ref (xw->gst_pipeline); + gst_object_ref (xw->gst_source); + gst_object_ref (xw->gst_tee); + gst_object_ref (xw->gst_fakesink); - /* signals */ - if (EQ (xw->type, Qwebkit)) - { - g_signal_connect (G_OBJECT (xw->widget_osr), - "load-changed", - G_CALLBACK (webkit_view_load_changed_cb), xw); - - g_signal_connect (G_OBJECT (webkit_context), - "download-started", - G_CALLBACK (webkit_download_cb), xw); - - g_signal_connect (G_OBJECT (xw->widget_osr), - "decide-policy", - G_CALLBACK - (webkit_decide_policy_cb), - xw); - - g_signal_connect (G_OBJECT (xw->widget_osr), - "mouse-target-changed", - G_CALLBACK (mouse_target_changed), - xw); - g_signal_connect (G_OBJECT (xw->widget_osr), - "create", - G_CALLBACK (webkit_create_cb), - xw); - g_signal_connect (G_OBJECT (xw->widget_osr), - "script-dialog", - G_CALLBACK (webkit_script_dialog_cb), - NULL); - g_signal_connect (G_OBJECT (xw->widget_osr), - "run-file-chooser", - G_CALLBACK (run_file_chooser_cb), - NULL); - } + gst_bin_add_many (GST_BIN (xw->gst_pipeline), xw->gst_source, + xw->gst_tee, queue, fakesink, NULL); - g_signal_connect (G_OBJECT (xw->widgetwindow_osr), "damage-event", - G_CALLBACK (offscreen_damage_event), xw); + gst_element_link_many (xw->gst_tee, queue, fakesink, NULL); - unblock_input (); + if (!gst_element_link (xw->gst_source, xw->gst_tee)) + emacs_abort (); + + if (!gst_element_set_state (xw->gst_pipeline, GST_STATE_PAUSED)) + emacs_abort (); } +#endif #elif defined NS_IMPL_COCOA nsxwidget_init (xw); #endif @@ -335,6 +394,9 @@ DEFUN ("xwidget-perform-lispy-event", f = SELECTED_FRAME (); #ifdef USE_GTK + if (!xw->xg_p) + return Qnil; + widget = gtk_window_get_focus (GTK_WINDOW (xw->widgetwindow_osr)); if (!widget) @@ -888,7 +950,8 @@ xwidget_button (struct xwidget_view *view, bool down_p, int x, int y, int button, int modifier_state, Time time) { - if (NILP (XXWIDGET (view->model)->buffer)) + if (NILP (XXWIDGET (view->model)->buffer) + || !XWIDGET_VIEW_GTK_P (view)) return; record_osr_embedder (view); @@ -947,7 +1010,7 @@ xwidget_motion_or_crossing (struct xwidget_view *view, const XEvent *event) int y; GtkWidget *target; - if (NILP (model->buffer)) + if (NILP (model->buffer) || !model->xg_p) return; xg_event = gdk_event_new (event->type == MotionNotify @@ -1117,7 +1180,17 @@ xwidget_expose (struct xwidget_view *xv) { struct xwidget *xw = XXWIDGET (xv->model); - xv_do_draw (xv, xw); + if (xw->xg_p) + xv_do_draw (xv, xw); +#ifdef HAVE_GSTREAMER + else if (EQ (xw->type, Qmedia) && XWIDGET_LIVE_P (xw)) + { + if (xv->wdesc != None) + XMoveWindow (xv->dpy, xv->internal_window, + -xv->clip_left, -xv->clip_top); + gst_video_overlay_expose (GST_VIDEO_OVERLAY (xv->video_sink)); + } +#endif } #endif /* USE_GTK */ @@ -1690,12 +1763,81 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) CopyFromParent, CWEventMask, &a); XLowerWindow (xv->dpy, xv->wdesc); XDefineCursor (xv->dpy, xv->wdesc, xv->cursor); - xv->cr_surface = cairo_xlib_surface_create (xv->dpy, - xv->wdesc, - FRAME_DISPLAY_INFO (s->f)->visual, - clip_right - clip_left, - clip_bottom - clip_top); - xv->cr_context = cairo_create (xv->cr_surface); + + if (XWIDGET_VIEW_GTK_P (xv)) + { + xv->cr_surface = cairo_xlib_surface_create (xv->dpy, + xv->wdesc, + FRAME_DISPLAY_INFO (s->f)->visual, + clip_right - clip_left, + clip_bottom - clip_top); + xv->cr_context = cairo_create (xv->cr_surface); + } +#ifdef HAVE_GSTREAMER + else + { + XFlush (xv->dpy); + + if (EQ (xww->type, Qmedia) && XWIDGET_LIVE_P (xww)) + { + GstPadTemplate *templ; + GstPad *sinkpad; + + a.event_mask = ExposureMask; + a.background_pixel = FRAME_BACKGROUND_PIXEL (xv->frame); + + xv->internal_window = XCreateWindow (xv->dpy, xv->wdesc, + -clip_left, -clip_top, + xww->width, xww->height, + 0, CopyFromParent, CopyFromParent, + CopyFromParent, + (CWEventMask | CWBackPixel), &a); + + XMapWindow (xv->dpy, xv->internal_window); + XFlush (xv->dpy); + + Fputhash (make_fixnum (xv->internal_window), xvw, x_window_to_xwv_map); + + templ = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (xww->gst_tee), + "src_%u"); + + xv->teepad = gst_element_request_pad (xww->gst_tee, + templ, NULL, NULL); + xv->video_sink = gst_element_factory_make ("xvimagesink", NULL); + xv->video_queue = gst_element_factory_make ("queue", NULL); + + gst_object_ref (xv->video_queue); + gst_object_ref (xv->video_sink); + + g_object_set_data (G_OBJECT (xv->video_sink), XG_WINDOW_HANDLE, + (gpointer) xv->internal_window); + g_object_set (G_OBJECT (xv->video_sink), "display", + FRAME_TERMINAL (xv->frame)->name, NULL); + + gst_video_overlay_handle_events (GST_VIDEO_OVERLAY (xv->video_sink), + FALSE); + + gst_bin_add_many (GST_BIN (xww->gst_pipeline), xv->video_queue, + xv->video_sink, NULL); + + if (!gst_element_link (xv->video_queue, xv->video_sink)) + emacs_abort (); + + gst_element_sync_state_with_parent (xv->video_queue); + gst_element_sync_state_with_parent (xv->video_sink); + + sinkpad = gst_element_get_static_pad (xv->video_queue, "sink"); + gst_pad_link (xv->teepad, sinkpad); + gst_object_unref (sinkpad); + + xv->sinkpad = sinkpad; + + gst_element_sync_state_with_parent (xww->gst_tee); + + xwidget_expose (xv); + } + } +#endif Fputhash (make_fixnum (xv->wdesc), xvw, x_window_to_xwv_map); moved = false; @@ -1709,8 +1851,9 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) XMoveResizeWindow (xv->dpy, xv->wdesc, x + clip_left, y + clip_top, clip_right - clip_left, clip_bottom - clip_top); XFlush (xv->dpy); - cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, - clip_bottom - clip_top); + if (XWIDGET_VIEW_GTK_P (xv)) + cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, + clip_bottom - clip_top); #elif defined NS_IMPL_COCOA nsxwidget_move_view (xv, x + clip_left, y + clip_top); #endif @@ -1740,8 +1883,10 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) clip_bottom - clip_top); } XFlush (xv->dpy); - cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, - clip_bottom - clip_top); + + if (xww->xg_p) + cairo_xlib_surface_set_size (xv->cr_surface, clip_right - clip_left, + clip_bottom - clip_top); } #elif defined NS_IMPL_COCOA nsxwidget_resize_view (xv, clip_right - clip_left, @@ -1765,7 +1910,10 @@ x_draw_xwidget_glyph_string (struct glyph_string *s) if (!xwidget_hidden (xv)) { #ifdef USE_GTK - gtk_widget_queue_draw (xww->widget_osr); + if (xww->xg_p) + gtk_widget_queue_draw (xww->widget_osr); + else + xwidget_expose (xv); #elif defined NS_IMPL_COCOA nsxwidget_set_needsdisplay (xv); #endif @@ -1788,6 +1936,12 @@ #define CHECK_WEBKIT_WIDGET(xw) \ if (NILP (xw->buffer) || !EQ (xw->type, Qwebkit)) \ error ("Not a WebKit widget") +#ifdef HAVE_GSTREAMER +#define CHECK_MEDIA_WIDGET(xw) \ + if (NILP (xw->buffer) || !EQ (xw->type, Qmedia)) \ + error ("Not a WebKit widget") +#endif + /* Macro that checks xwidget hold webkit web view first. */ #define WEBKIT_FN_INIT() \ CHECK_LIVE_XWIDGET (xwidget); \ @@ -1989,6 +2143,14 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0, #ifdef USE_GTK xv->just_resized = true; SET_FRAME_GARBAGED (xv->frame); +#ifdef HAVE_GSTREAMER + if (EQ (xw->type, Qmedia)) + { + XResizeWindow (xv->dpy, xv->internal_window, + xw->width, xw->height); + } +#endif + #else wset_redisplay (XWINDOW (xv->w)); #endif @@ -2000,7 +2162,7 @@ DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0, /* If there is an offscreen widget resize it first. */ #ifdef USE_GTK - if (xw->widget_osr) + if (xw->xg_p && xw->widget_osr) { gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width, xw->height); @@ -2030,9 +2192,14 @@ DEFUN ("xwidget-size-request", { CHECK_LIVE_XWIDGET (xwidget); #ifdef USE_GTK - GtkRequisition requisition; - gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition); - return list2i (requisition.width, requisition.height); + if (XXWIDGET (xwidget)->xg_p) + { + GtkRequisition requisition; + gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition); + return list2i (requisition.width, requisition.height); + } + + return Qnil; #elif defined NS_IMPL_COCOA return nsxwidget_get_size (XXWIDGET (xwidget)); #endif @@ -2115,18 +2282,29 @@ DEFUN ("delete-xwidget-view", #ifdef USE_GTK struct xwidget *xw = XXWIDGET (xv->model); GdkWindow *w; - +#ifdef HAVE_GSTREAMER + block_input (); + if (XWIDGET_LIVE_P (xw) && EQ (xw->type, Qmedia)) + unlink_gst_xwidget_view (xv); + unblock_input (); +#endif if (xv->wdesc != None) { block_input (); - cairo_destroy (xv->cr_context); - cairo_surface_destroy (xv->cr_surface); + if (XWIDGET_VIEW_GTK_P (xv)) + { + cairo_destroy (xv->cr_context); + cairo_surface_destroy (xv->cr_surface); + } XDestroyWindow (xv->dpy, xv->wdesc); Fremhash (make_fixnum (xv->wdesc), x_window_to_xwv_map); + + if (XWIDGET_LIVE_P (xw) && EQ (xw->type, Qmedia)) + Fremhash (make_fixnum (xv->internal_window), x_window_to_xwv_map); unblock_input (); } - if (xw->embedder_view == xv && !NILP (xw->buffer)) + if (xw->xg_p && xw->embedder_view == xv && !NILP (xw->buffer)) { w = gtk_widget_get_window (xw->widgetwindow_osr); @@ -2555,6 +2733,77 @@ DEFUN ("xwidget-webkit-back-forward-list", Fxwidget_webkit_back_forward_list, return list3 (back, here, forward); } + +#ifdef HAVE_GSTREAMER +DEFUN ("xwidget-media-pause", Fxwidget_media_pause, Sxwidget_media_pause, + 1, 1, 0, doc: /* Pause specified media XWIDGET. +This causes playback to stop until you resume it using +`xwidget-media-play'. */) + (Lisp_Object xwidget) +{ + struct xwidget *xw; + struct xwidget_view *xv; + Lisp_Object tem; + + CHECK_LIVE_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + CHECK_MEDIA_WIDGET (xw); + + block_input (); + gst_element_set_state (xw->gst_pipeline, GST_STATE_PAUSED); + gst_element_sync_state_with_parent (xw->gst_tee); + gst_element_sync_state_with_parent (xw->gst_fakesink); + + for (tem = internal_xwidget_view_list; CONSP (tem); tem = XCDR (tem)) + { + xv = XXWIDGET_VIEW (XCAR (tem)); + + if (EQ (xv->model, xwidget)) + { + gst_element_sync_state_with_parent (xv->video_queue); + gst_element_sync_state_with_parent (xv->video_sink); + } + } + + unblock_input (); + + return Qnil; +} + +DEFUN ("xwidget-media-play", Fxwidget_media_play, Sxwidget_media_play, + 1, 1, 0, doc: /* Resume playback of specified media XWIDGET. */) + (Lisp_Object xwidget) +{ + struct xwidget *xw; + struct xwidget_view *xv; + Lisp_Object tem; + + CHECK_LIVE_XWIDGET (xwidget); + xw = XXWIDGET (xwidget); + CHECK_MEDIA_WIDGET (xw); + + block_input (); + gst_element_set_state (xw->gst_pipeline, GST_STATE_PLAYING); + gst_element_sync_state_with_parent (xw->gst_tee); + gst_element_sync_state_with_parent (xw->gst_fakesink); + + for (tem = internal_xwidget_view_list; CONSP (tem); tem = XCDR (tem)) + { + xv = XXWIDGET_VIEW (XCAR (tem)); + + if (EQ (xv->model, xwidget)) + { + gst_element_sync_state_with_parent (xv->video_queue); + gst_element_sync_state_with_parent (xv->video_sink); + } + } + + gst_element_sync_state_with_parent (xw->gst_tee); + unblock_input (); + + return Qnil; +} +#endif #endif void @@ -2584,6 +2833,7 @@ syms_of_xwidget (void) defsubr (&Sxwidget_webkit_zoom); defsubr (&Sxwidget_webkit_execute_script); DEFSYM (Qwebkit, "webkit"); + DEFSYM (Qmedia, "media"); defsubr (&Sxwidget_size_request); defsubr (&Sdelete_xwidget_view); @@ -2600,6 +2850,10 @@ syms_of_xwidget (void) #ifdef USE_GTK defsubr (&Sxwidget_webkit_load_html); defsubr (&Sxwidget_webkit_back_forward_list); +#ifdef HAVE_GSTREAMER + defsubr (&Sxwidget_media_pause); + defsubr (&Sxwidget_media_play); +#endif #endif defsubr (&Skill_xwidget); @@ -2862,7 +3116,7 @@ kill_xwidget (struct xwidget *xw) #ifdef USE_GTK xw->buffer = Qnil; - if (xw->widget_osr && xw->widgetwindow_osr) + if (xw->xg_p && xw->widget_osr && xw->widgetwindow_osr) { gtk_widget_destroy (xw->widget_osr); gtk_widget_destroy (xw->widgetwindow_osr); @@ -2882,6 +3136,26 @@ kill_xwidget (struct xwidget *xw) } } +#ifdef HAVE_GSTREAMER + Lisp_Object tem; + + if (EQ (xw->type, Qmedia)) + { + for (tem = internal_xwidget_view_list; CONSP (tem); + tem = XCDR (tem)) + { + struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tem)); + + if (XXWIDGET (xv->model) == xw) + unlink_gst_xwidget_view (xv); + } + gst_object_unref (xw->gst_tee); + gst_object_unref (xw->gst_pipeline); + gst_object_unref (xw->gst_source); + gst_object_unref (xw->gst_fakesink); + } +#endif + xw->widget_osr = NULL; xw->widgetwindow_osr = NULL; xw->find_text = NULL; @@ -2908,3 +3182,59 @@ kill_buffer_xwidgets (Lisp_Object buffer) } } } + +#ifdef HAVE_GSTREAMER +static GstBusSyncReply +gst_create_window (GstBus *bus, GstMessage *msg, gpointer user_data) +{ + GstObject *elm; + Window wdesc; + + if (!gst_is_video_overlay_prepare_window_handle_message (msg)) + return GST_BUS_PASS; + + elm = GST_MESSAGE_SRC (msg); + wdesc = (Window) g_object_get_data (G_OBJECT (elm), XG_WINDOW_HANDLE); + + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (elm), wdesc); + + return GST_BUS_DROP; +} + +static gboolean +gst_message_cb (GstBus *bus, GstMessage *message, gpointer user_data) +{ + return TRUE; +} + +static bool +check_gstreamer_dependencies (void) +{ + GstRegistry *registry = gst_registry_get (); + + return (gst_registry_find_feature (registry, "xvimagesink", + GST_TYPE_ELEMENT_FACTORY) + && gst_registry_find_feature (registry, "queue", + GST_TYPE_ELEMENT_FACTORY) + && gst_registry_find_feature (registry, "videotestsrc", + GST_TYPE_ELEMENT_FACTORY) + && gst_registry_find_feature (registry, "tee", + GST_TYPE_ELEMENT_FACTORY)); +} + +static void +unlink_gst_xwidget_view (struct xwidget_view *xv) +{ + struct xwidget *xw = XXWIDGET (xv->model); + + gst_pad_unlink (xv->teepad, xv->sinkpad); + gst_element_unlink (xv->video_queue, xv->video_sink); + + gst_element_set_state (xv->video_queue, GST_STATE_NULL); + gst_element_set_state (xv->video_sink, GST_STATE_NULL); + gst_bin_remove_many (GST_BIN (xw->gst_pipeline), xv->video_sink, + xv->video_queue, NULL); + gst_object_unref (xv->video_sink); + gst_object_unref (xv->video_queue); +} +#endif diff --git a/src/xwidget.h b/src/xwidget.h index 78fe865dd8..66f7fb2c4c 100644 --- a/src/xwidget.h +++ b/src/xwidget.h @@ -39,6 +39,10 @@ #define XWIDGET_H_INCLUDED #import "nsxwidget.h" #endif +#ifdef HAVE_GSTREAMER +#include +#endif + struct xwidget { union vectorlike_header header; @@ -65,6 +69,16 @@ #define XWIDGET_H_INCLUDED char *find_text; #if defined (USE_GTK) + /* Whether or not the widget is used to display GTK widgets. */ + bool xg_p; + +#ifdef HAVE_GSTREAMER + GstElement *gst_tee; + GstElement *gst_pipeline; + GstElement *gst_source; + GstElement *gst_fakesink; +#endif + /* For offscreen widgets, unused if not osr. */ GtkWidget *widget_osr; GtkWidget *widgetwindow_osr; @@ -112,6 +126,14 @@ #define XWIDGET_H_INCLUDED Emacs_Cursor cursor; struct frame *frame; +#ifdef HAVE_GSTREAMER + Window internal_window; + GstPad *teepad; + GstPad *sinkpad; + GstElement *video_sink; + GstElement *video_queue; +#endif + cairo_surface_t *cr_surface; cairo_t *cr_context; int just_resized; @@ -159,6 +181,9 @@ #define XXWIDGET_VIEW(a) (eassert (XWIDGET_VIEW_P (a)), \ #define CHECK_XWIDGET_VIEW(x) \ CHECK_TYPE (XWIDGET_VIEW_P (x), Qxwidget_view_p, x) +#define XWIDGET_VIEW_GTK_P(x) \ + XXWIDGET ((x)->model)->xg_p + #define XG_XWIDGET "emacs_xwidget" #define XG_XWIDGET_VIEW "emacs_xwidget_view" -- 2.31.1