#define GST_CAT_DEFAULT gst_debug_qt_gl_sink
GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
+static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface);
static void gst_qt_sink_finalize (GObject * object);
static void gst_qt_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * param_spec);
#define gst_qt_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstQtSink, gst_qt_sink,
GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT,
- "qtsink", 0, "Qt Video Sink"));
+ "qtsink", 0, "Qt Video Sink");
+ G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+ gst_qt_sink_navigation_interface_init));
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qmlglsink, "qmlglsink",
GST_RANK_NONE, GST_TYPE_QT_SINK, qt5_element_init (plugin));
gst_qt_sink_init (GstQtSink * qt_sink)
{
qt_sink->widget = QSharedPointer<QtGLVideoItemInterface>();
+ if (qt_sink->widget)
+ qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
}
static void
switch (prop_id) {
case PROP_WIDGET: {
QtGLVideoItem *qt_item = static_cast<QtGLVideoItem *> (g_value_get_pointer (value));
- if (qt_item)
+ if (qt_item) {
qt_sink->widget = qt_item->getInterface();
- else
+ if (qt_sink->widget) {
+ qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+ }
+ } else {
qt_sink->widget.clear();
+ }
break;
}
case PROP_FORCE_ASPECT_RATIO:
return FALSE;
}
}
+
+static void
+gst_qt_sink_navigation_send_event (GstNavigation * navigation,
+ GstStructure * structure)
+{
+ GstQtSink *qt_sink = GST_QT_SINK (navigation);
+ GstEvent *event;
+ GstPad *pad;
+
+ event = gst_event_new_navigation (structure);
+ pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (qt_sink));
+
+ GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT, structure);
+
+ if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+ if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+ /* If upstream didn't handle the event we'll post a message with it
+ * for the application in case it wants to do something with it */
+ gst_element_post_message (GST_ELEMENT_CAST (qt_sink),
+ gst_navigation_message_new_event (GST_OBJECT_CAST (qt_sink), event));
+ }
+ gst_event_unref (event);
+ gst_object_unref (pad);
+ }
+}
+
+static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+ iface->send_event = gst_qt_sink_navigation_send_event;
+}
#include <QtQuick/QSGSimpleTextureNode>
/**
- * SECTION:gtkgstglwidget
- * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers
- * @see_also: #GtkGLArea, #GstBuffer
+ * SECTION:QtGLVideoItem
+ * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers
*
* #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers.
*/
gboolean force_aspect_ratio;
gint par_n, par_d;
+ GWeakRef sink;
+
gint display_width;
gint display_height;
g_mutex_init (&this->priv->lock);
+ g_weak_ref_init (&priv->sink, NULL);
+
this->priv->display = gst_qt_get_gl_display(TRUE);
connect(this, SIGNAL(windowChanged(QQuickWindow*)), this,
this->proxy = QSharedPointer<QtGLVideoItemInterface>(new QtGLVideoItemInterface(this));
+ setFlag(ItemHasContents, true);
+ setAcceptedMouseButtons(Qt::AllButtons);
+ setAcceptHoverEvents(true);
+
GST_DEBUG ("%p init Qt Video Item", this);
}
gst_buffer_replace (&this->priv->buffer, NULL);
gst_caps_replace (&this->priv->caps, NULL);
+
+ g_weak_ref_clear (&this->priv->sink);
+
g_free (this->priv);
this->priv = NULL;
}
return texNode;
}
+/* This method has to be invoked with the the priv->lock taken */
+void
+QtGLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result)
+{
+ if (this->priv->force_aspect_ratio) {
+ GstVideoRectangle src, dst;
+
+ src.x = 0;
+ src.y = 0;
+ src.w = this->priv->display_width;
+ src.h = this->priv->display_height;
+
+ dst.x = 0;
+ dst.y = 0;
+ dst.w = size().width();
+ dst.h = size().height();
+
+ gst_video_sink_center_rect (src, dst, result, TRUE);
+ } else {
+ result->x = 0;
+ result->y = 0;
+ result->w = size().width();
+ result->h = size().height();
+ }
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+QPointF
+QtGLVideoItem::mapPointToStreamSize(QPointF pos)
+{
+ gdouble stream_width, stream_height;
+ GstVideoRectangle result;
+ double stream_x, stream_y;
+ double x, y;
+
+ fitStreamToAllocatedSize(&result);
+
+ stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info);
+ stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info);
+ x = pos.x();
+ y = pos.y();
+
+ /* from display coordinates to stream coordinates */
+ if (result.w > 0)
+ stream_x = (x - result.x) / result.w * stream_width;
+ else
+ stream_x = 0.;
+
+ /* clip to stream size */
+ stream_x = CLAMP(stream_x, 0., stream_width);
+
+ /* same for y-axis */
+ if (result.h > 0)
+ stream_y = (y - result.y) / result.h * stream_height;
+ else
+ stream_y = 0.;
+
+ stream_y = CLAMP(stream_y, 0., stream_height);
+ GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y);
+ return QPointF(stream_x, stream_y);
+}
+
+void
+QtGLVideoItem::wheelEvent(QWheelEvent * event)
+{
+ g_mutex_lock (&this->priv->lock);
+ QPoint delta = event->angleDelta();
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+#if (QT_VERSION >= QT_VERSION_CHECK (5, 14, 0))
+ auto position = event->position();
+#else
+ auto position = *event;
+#endif
+ gst_navigation_send_mouse_scroll_event (GST_NAVIGATION (element),
+ position.x(), position.y(), delta.x(), delta.y());
+ g_object_unref (element);
+ }
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::hoverEnterEvent(QHoverEvent *)
+{
+ m_hovering = true;
+}
+
+void
+QtGLVideoItem::hoverLeaveEvent(QHoverEvent *)
+{
+ m_hovering = false;
+}
+
+void
+QtGLVideoItem::hoverMoveEvent(QHoverEvent * event)
+{
+ if (!m_hovering)
+ return;
+
+ int button = !!m_mousePressedButton;
+ g_mutex_lock (&this->priv->lock);
+ if (event->pos() != event->oldPos()) {
+ QPointF pos = mapPointToStreamSize(event->pos());
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+ gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
+ button, pos.x(), pos.y());
+ g_object_unref (element);
+ }
+ }
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::sendMouseEvent(QMouseEvent * event, const gchar * type)
+{
+ int button = 0;
+ switch (event->button()) {
+ case Qt::LeftButton:
+ button = 1;
+ break;
+ case Qt::RightButton:
+ button = 2;
+ break;
+ default:
+ break;
+ }
+ m_mousePressedButton = button;
+ g_mutex_lock (&this->priv->lock);
+
+ QPointF pos = mapPointToStreamSize(event->pos());
+ gchar* event_type = g_strconcat ("mouse-button-", type, NULL);
+ GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+ if (element != NULL) {
+ gst_navigation_send_mouse_event (GST_NAVIGATION (element), event_type,
+ button, pos.x(), pos.y());
+ g_object_unref (element);
+ }
+
+ g_free (event_type);
+ g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::mousePressEvent(QMouseEvent * event)
+{
+ forceActiveFocus();
+ sendMouseEvent(event, "press");
+}
+
+void
+QtGLVideoItem::mouseReleaseEvent(QMouseEvent * event)
+{
+ sendMouseEvent(event, "release");
+}
+
static void
_reset (QtGLVideoItem * qt_item)
{
}
void
+QtGLVideoItemInterface::setSink (GstElement * sink)
+{
+ QMutexLocker locker(&lock);
+ if (qt_item == NULL)
+ return;
+
+ g_mutex_lock (&qt_item->priv->lock);
+ g_weak_ref_set (&qt_item->priv->sink, sink);
+ g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
QtGLVideoItemInterface::setBuffer (GstBuffer * buffer)
{
QMutexLocker locker(&lock);