Commit 15c78e18 authored by Emmanuel Gil Peyrot's avatar Emmanuel Gil Peyrot

WIP: Jingle GTK+.

parent b6957029
Pipeline #4737 failed with stages
in 1 minute and 43 seconds
......@@ -74,6 +74,7 @@ from gajim.gtk.util import format_location
from gajim.gtk.util import get_activity_icon_name
from gajim.gtk.util import make_href_markup
from gajim.gtk.const import ControlType
from gajim.gtk import gstreamer
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
......@@ -236,6 +237,7 @@ class ChatControl(ChatControlBase):
('receipt-received', ged.GUI1, self._receipt_received),
('message-error', ged.GUI1, self._on_message_error),
('zeroconf-error', ged.GUI1, self._on_zeroconf_error),
('jingle-session-started', ged.GUI1, self._on_jingle_session_started),
])
if self._type.is_chat:
......@@ -632,6 +634,15 @@ class ChatControl(ChatControlBase):
def _on_update_roster_avatar(self, obj):
self.show_avatar()
def _on_jingle_session_started(self, event):
content = event.content
content.out_parent = self.xml.outgoing_viewport
sink_other, widget_other, name = gstreamer.create_gtk_widget()
sink_self, widget_self, name = gstreamer.create_gtk_widget()
self.xml.incoming_viewport.add(widget_other)
self.xml.outgoing_viewport.add(widget_self)
content.do_setup(sink_self, sink_other)
def _nec_ping(self, event):
if self.contact != event.contact:
return
......@@ -699,7 +710,7 @@ class ChatControl(ChatControlBase):
# update MessageWindow._controls
self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
def stop_jingle(self, sid=None, _reason=None):
def stop_jingle(self, sid=None, reason=None):
audio_sid = self.jingle['audio'].sid
video_sid = self.jingle['video'].sid
if audio_sid and sid in (audio_sid, None):
......@@ -909,20 +920,12 @@ class ChatControl(ChatControlBase):
fixed = self.xml.outgoing_fixed
fixed.set_no_show_all(False)
video_hbox.show_all()
out_da = self.xml.outgoing_drawingarea
out_da.realize()
if os.name == 'nt':
out_xid = out_da.get_window().handle
else:
out_xid = out_da.get_window().get_xid()
out_parent = self.xml.outgoing_viewport
else:
out_xid = None
out_parent = None
video_hbox.show_all()
in_da = self.xml.incoming_drawingarea
in_da.realize()
in_xid = in_da.get_window().get_xid()
sid = con.get_module('Jingle').start_video(
self.contact.get_full_jid(), in_xid, out_xid)
self.contact.get_full_jid(), out_parent)
else:
sid = con.start_audio(self.contact.get_full_jid())
self.jingle[jingle_type].set_state('connecting', sid)
......
......@@ -409,13 +409,14 @@ class JingleAudio(JingleRTPContent):
# The following is needed for farstream to process ICE requests:
self.pipeline.set_state(Gst.State.PLAYING)
Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'audio-graph')
class JingleVideo(JingleRTPContent):
def __init__(self, session, transport=None, in_xid=0, out_xid=0):
def __init__(self, session, transport=None, out_parent=None):
JingleRTPContent.__init__(self, session, 'video', transport)
self.in_xid = in_xid
self.out_xid = out_xid
self.out_parent = out_parent
# XXX: remove
self.out_xid_set = False
self.setup_stream()
......@@ -428,6 +429,8 @@ class JingleVideo(JingleRTPContent):
bus.enable_sync_message_emission()
bus.connect('sync-message::element', self._on_sync_message)
def do_setup(self, self_display_sink, other_sink):
# the local parts
if app.config.get('video_framerate'):
framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
......@@ -442,26 +445,23 @@ class JingleVideo(JingleRTPContent):
video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
else:
video_size = ''
if app.config.get('video_see_self'):
tee = '! tee name=t ! queue ! videoscale ! ' + \
'video/x-raw,width=160,height=120 ! videoconvert ! ' + \
'%s t. ! queue ' % app.config.get(
'video_output_device')
tee = '! tee name=split ! queue name=self-display-queue split. ! queue name=network-queue'
else:
tee = ''
self.src_bin = self.make_bin_from_config('video_input_device',
'%%s %s! %svideoscale ! %svideoconvert' %
(tee, framerate, video_size),
'%%s %s' % tee,
_("video input"))
self.pipeline.add(self.src_bin)
if app.config.get('video_see_self'):
self.pipeline.add(self_display_sink)
self_display_queue = self.src_bin.get_by_name('self-display-queue')
self_display_queue.get_static_pad('src').link_maybe_ghosting(self_display_sink.get_static_pad('sink'))
self.pipeline.set_state(Gst.State.PLAYING)
self.sink = self.make_bin_from_config('video_output_device',
'videoscale ! videoconvert ! %s',
_("video output"))
self.sink = other_sink
self.pipeline.add(self.sink)
self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
......@@ -469,13 +469,16 @@ class JingleVideo(JingleRTPContent):
# The following is needed for farstream to process ICE requests:
self.pipeline.set_state(Gst.State.PLAYING)
Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'video-graph')
def _on_sync_message(self, bus, message):
if message.get_structure() is None:
structure = message.get_structure()
if structure is None:
return False
if message.get_structure().get_name() == 'prepare-window-handle':
message.src.set_property('force-aspect-ratio', True)
if structure.get_name() == 'prepare-window-handle':
imagesink = message.src
imagesink.set_property('force-aspect-ratio', True)
print(imagesink)
if app.config.get('video_see_self') and not self.out_xid_set:
imagesink.set_window_handle(self.out_xid)
self.out_xid_set = True
......
......@@ -36,6 +36,7 @@ from gajim.common import app
from gajim.common.jingle_transport import get_jingle_transport, JingleTransportIBB
from gajim.common.jingle_content import get_jingle_content, JingleContentSetupException
from gajim.common.jingle_ft import State
from gajim.common.nec import NetworkEvent
from gajim.common.connection_handlers_events import (
FilesProp, JingleRequestReceivedEvent, JingleDisconnectedReceivedEvent,
JingleTransferCancelledEvent, JingleConnectedReceivedEvent,
......@@ -642,6 +643,8 @@ class JingleSession:
self.add_content(element['name'],
content, 'peer')
contents.append(content)
app.nec.push_incoming_event(
NetworkEvent('jingle-session-started', content=content))
else:
reasons.add('unsupported-transports')
contents_rejected.append((element['name'], 'peer'))
......
......@@ -165,18 +165,18 @@ class Jingle(BaseModule):
jingle.start_session()
return jingle.sid
def start_video(self, jid, in_xid, out_xid):
def start_video(self, jid, out_parent):
if self.get_jingle_session(jid, media='video'):
return self.get_jingle_session(jid, media='video').sid
jingle = self.get_jingle_session(jid, media='audio')
if jingle:
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
out_xid=out_xid))
video = JingleVideo(jingle, out_parent=out_parent)
jingle.add_content('video', video)
else:
jingle = JingleSession(self._con, weinitiate=True, jid=jid)
self._sessions[jingle.sid] = jingle
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
out_xid=out_xid))
video = JingleVideo(jingle, out_parent=out_parent)
jingle.add_content('video', video)
jingle.start_session()
return jingle.sid
......
......@@ -945,16 +945,13 @@ microphone-sensitivity-high-symbolic</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<child>
<object class="GtkViewport" id="viewport1">
<object class="GtkViewport" id="outgoing_viewport">
<property name="width_request">160</property>
<property name="height_request">120</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkDrawingArea" id="outgoing_drawingarea">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<placeholder/>
</child>
</object>
</child>
......@@ -971,16 +968,11 @@ microphone-sensitivity-high-symbolic</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkViewport" id="viewport2">
<object class="GtkViewport" id="incoming_viewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkDrawingArea" id="incoming_drawingarea">
<property name="width_request">320</property>
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<placeholder/>
</child>
</object>
<packing>
......
......@@ -1100,34 +1100,13 @@ class VoIPCallReceivedDialog:
fixed = ctrl.xml.get_object('outgoing_fixed')
fixed.set_no_show_all(False)
video_hbox.show_all()
ctrl.xml.get_object('incoming_drawingarea').realize()
if os.name == 'nt':
in_xid = ctrl.xml.get_object('incoming_drawingarea').\
get_window().handle
else:
in_xid = ctrl.xml.get_object('incoming_drawingarea').\
get_property('window').get_xid()
incoming_drawingarea = ctrl.xml.get_object('incoming_drawingarea')
content = session.get_content('video')
# move outgoing stream to chat window
if app.config.get('video_see_self'):
ctrl.xml.get_object('outgoing_drawingarea').realize()
if os.name == 'nt':
out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
get_window().handle
else:
out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
get_property('window').get_xid()
b = content.src_bin
for e in b.children:
if not e.get_name().startswith('autovideosink'):
continue
for f in e.children:
if f.get_name().startswith('autovideosink'):
f.set_window_handle(out_xid)
content.out_xid = out_xid
break
break
content.in_xid = in_xid
outgoing_drawingarea = ctrl.xml.get_object('outgoing_drawingarea')
content.out_parent = outgoing_drawingarea
content.in_parent = incoming_drawingarea
ctrl.set_video_state('connecting', self.sid)
# Now, accept the content/sessions.
# This should be done after the chat control is running
......
from typing import Tuple, Optional
from gi.repository import Gst
from gi.repository import Gtk
def create_gtk_widget() -> Tuple[Optional[Gst.Element], Optional[Gtk.Widget], Optional[str]]:
gtkglsink = Gst.ElementFactory.make('gtkglsink', None)
if gtkglsink is not None:
glsinkbin = Gst.ElementFactory.make('glsinkbin', None)
if glsinkbin is None:
return None, None, None
glsinkbin.set_property('sink', gtkglsink)
sink = glsinkbin
widget = gtkglsink.get_property('widget')
name = 'gtkglsink'
else:
sink = Gst.ElementFactory.make('gtksink', None)
if sink is None:
return None, None, None
widget = sink.get_property('widget')
name = 'gtksink'
widget.set_visible(True)
widget.set_property('expand', True)
return sink, widget, name
......@@ -38,6 +38,7 @@ from gajim.gtk.util import open_window
from gajim.gtk.dialogs import AspellDictError
from gajim.gtk.sounds import ManageSounds
from gajim.gtk.const import ControlType
from gajim.gtk import gstreamer
try:
from gajim.common.multimedia_helpers import AudioInputManager, AudioOutputManager
......@@ -384,7 +385,7 @@ class Preferences(Gtk.ApplicationWindow):
def on_av_map(tab):
label = self._ui.selected_video_output
sink, widget, name = self.setup_video_output()
sink, widget, name = gstreamer.create_gtk_widget()
if sink is None:
log.error('Failed to obtain a working Gstreamer GTK+ sink, '
'video support will be disabled')
......@@ -508,27 +509,6 @@ class Preferences(Gtk.ApplicationWindow):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
@staticmethod
def setup_video_output():
gtkglsink = Gst.ElementFactory.make('gtkglsink', None)
if gtkglsink is not None:
glsinkbin = Gst.ElementFactory.make('glsinkbin', None)
if glsinkbin is None:
return None, None, None
glsinkbin.set_property('sink', gtkglsink)
sink = glsinkbin
widget = gtkglsink.get_property('widget')
name = 'gtkglsink'
else:
sink = Gst.ElementFactory.make('gtksink', None)
if sink is None:
return None, None, None
widget = sink.get_property('widget')
name = 'gtksink'
widget.set_visible(True)
widget.set_property('expand', True)
return sink, widget, name
def get_per_account_option(self, opt):
"""
Return the value of the option opt if it's the same in all accounts else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment