Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
5777b429
by Emmanuel Gil Peyrot at 2020-03-17T19:32:39+01:00
-
3c85325c
by Emmanuel Gil Peyrot at 2020-03-17T19:33:00+01:00
8 changed files:
- gajim/chat_control.py
- gajim/common/jingle_rtp.py
- gajim/common/modules/jingle.py
- gajim/data/gui/chat_control.ui
- gajim/dialogs.py
- + gajim/gtk/gstreamer.py
- gajim/gtk/preferences.py
- gajim/gui_interface.py
Changes:
| ... | ... | @@ -79,6 +79,20 @@ from gajim.command_system.implementation.hosts import ChatCommands |
| 79 | 79 |
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
|
| 80 | 80 |
from gajim.chat_control_base import ChatControlBase
|
| 81 | 81 |
|
| 82 |
+ |
|
| 83 |
+class JingleState:
|
|
| 84 |
+ __slots__ = ('sid', 'state', 'available', 'banner_image', 'action', 'set_state', 'update')
|
|
| 85 |
+ |
|
| 86 |
+ def __init__(self, state, banner_image, set_state, update):
|
|
| 87 |
+ self.sid = None
|
|
| 88 |
+ self.state = state
|
|
| 89 |
+ self.available = False
|
|
| 90 |
+ self.banner_image = banner_image
|
|
| 91 |
+ self.action = None
|
|
| 92 |
+ self.set_state = set_state
|
|
| 93 |
+ self.update = update
|
|
| 94 |
+ |
|
| 95 |
+ |
|
| 82 | 96 |
################################################################################
|
| 83 | 97 |
class ChatControl(ChatControlBase):
|
| 84 | 98 |
"""
|
| ... | ... | @@ -126,12 +140,10 @@ class ChatControl(ChatControlBase): |
| 126 | 140 |
# Settings menu
|
| 127 | 141 |
self.xml.settings_menu.set_menu_model(self.control_menu)
|
| 128 | 142 |
|
| 129 |
- self.audio_sid = None
|
|
| 130 |
- self.audio_state = self.JINGLE_STATE_NULL
|
|
| 131 |
- self.audio_available = False
|
|
| 132 |
- self.video_sid = None
|
|
| 133 |
- self.video_state = self.JINGLE_STATE_NULL
|
|
| 134 |
- self.video_available = False
|
|
| 143 |
+ self.jingle = {
|
|
| 144 |
+ 'audio': JingleState(self.JINGLE_STATE_NULL, self.xml.audio_banner_image, self.set_audio_state, self.update_audio),
|
|
| 145 |
+ 'video': JingleState(self.JINGLE_STATE_NULL, self.xml.video_banner_image, self.set_video_state, self.update_video),
|
|
| 146 |
+ }
|
|
| 135 | 147 |
|
| 136 | 148 |
self.update_toolbar()
|
| 137 | 149 |
self.update_all_pep_types()
|
| ... | ... | @@ -254,17 +266,19 @@ class ChatControl(ChatControlBase): |
| 254 | 266 |
act.connect("activate", func)
|
| 255 | 267 |
self.parent_win.window.add_action(act)
|
| 256 | 268 |
|
| 257 |
- self.audio_action = Gio.SimpleAction.new_stateful(
|
|
| 269 |
+ audio = self.jingle['audio']
|
|
| 270 |
+ audio.action = Gio.SimpleAction.new_stateful(
|
|
| 258 | 271 |
'toggle-audio-' + self.control_id, None,
|
| 259 | 272 |
GLib.Variant.new_boolean(False))
|
| 260 |
- self.audio_action.connect('change-state', self._on_audio)
|
|
| 261 |
- self.parent_win.window.add_action(self.audio_action)
|
|
| 273 |
+ audio.action.connect('change-state', self._on_audio)
|
|
| 274 |
+ self.parent_win.window.add_action(audio.action)
|
|
| 262 | 275 |
|
| 263 |
- self.video_action = Gio.SimpleAction.new_stateful(
|
|
| 276 |
+ video = self.jingle['video']
|
|
| 277 |
+ video.action = Gio.SimpleAction.new_stateful(
|
|
| 264 | 278 |
'toggle-video-' + self.control_id,
|
| 265 | 279 |
None, GLib.Variant.new_boolean(False))
|
| 266 |
- self.video_action.connect('change-state', self._on_video)
|
|
| 267 |
- self.parent_win.window.add_action(self.video_action)
|
|
| 280 |
+ video.action.connect('change-state', self._on_video)
|
|
| 281 |
+ self.parent_win.window.add_action(video.action)
|
|
| 268 | 282 |
|
| 269 | 283 |
default_chatstate = app.config.get('send_chatstate_default')
|
| 270 | 284 |
chatstate = app.config.get_per(
|
| ... | ... | @@ -294,11 +308,11 @@ class ChatControl(ChatControlBase): |
| 294 | 308 |
|
| 295 | 309 |
# Audio
|
| 296 | 310 |
win.lookup_action('toggle-audio-' + self.control_id).set_enabled(
|
| 297 |
- online and self.audio_available)
|
|
| 311 |
+ online and self.jingle['audio'].available)
|
|
| 298 | 312 |
|
| 299 | 313 |
# Video
|
| 300 | 314 |
win.lookup_action('toggle-video-' + self.control_id).set_enabled(
|
| 301 |
- online and self.video_available)
|
|
| 315 |
+ online and self.jingle['video'].available)
|
|
| 302 | 316 |
|
| 303 | 317 |
# Send file (HTTP File Upload)
|
| 304 | 318 |
httpupload = win.lookup_action(
|
| ... | ... | @@ -419,15 +433,17 @@ class ChatControl(ChatControlBase): |
| 419 | 433 |
_('This contact does not support HTML'))
|
| 420 | 434 |
|
| 421 | 435 |
# Jingle detection
|
| 436 |
+ jingle_audio = self.jingle['audio']
|
|
| 437 |
+ jingle_video = self.jingle['video']
|
|
| 422 | 438 |
if self.contact.supports(NS_JINGLE_ICE_UDP) and \
|
| 423 | 439 |
app.is_installed('FARSTREAM') and self.contact.resource:
|
| 424 |
- self.audio_available = self.contact.supports(NS_JINGLE_RTP_AUDIO)
|
|
| 425 |
- self.video_available = self.contact.supports(NS_JINGLE_RTP_VIDEO)
|
|
| 440 |
+ jingle_audio.available = self.contact.supports(NS_JINGLE_RTP_AUDIO)
|
|
| 441 |
+ jingle_video.available = self.contact.supports(NS_JINGLE_RTP_VIDEO)
|
|
| 426 | 442 |
else:
|
| 427 |
- if self.video_available or self.audio_available:
|
|
| 443 |
+ if jingle_video.available or jingle_audio.available:
|
|
| 428 | 444 |
self.stop_jingle()
|
| 429 |
- self.video_available = False
|
|
| 430 |
- self.audio_available = False
|
|
| 445 |
+ jingle_video.available = False
|
|
| 446 |
+ jingle_audio.available = False
|
|
| 431 | 447 |
|
| 432 | 448 |
def update_all_pep_types(self):
|
| 433 | 449 |
self._update_pep(PEPEventType.LOCATION)
|
| ... | ... | @@ -630,11 +646,10 @@ class ChatControl(ChatControlBase): |
| 630 | 646 |
elif event.name == 'ping-error':
|
| 631 | 647 |
self.add_info_message(_('Error.'))
|
| 632 | 648 |
|
| 633 |
- def _update_jingle(self, jingle_type):
|
|
| 634 |
- if jingle_type not in ('audio', 'video'):
|
|
| 635 |
- return
|
|
| 636 |
- banner_image = getattr(self, '_' + jingle_type + '_banner_image')
|
|
| 637 |
- state = getattr(self, jingle_type + '_state')
|
|
| 649 |
+ def _update_jingle(self, jingle_type: str) -> None:
|
|
| 650 |
+ jingle = self.jingle[jingle_type]
|
|
| 651 |
+ banner_image = jingle.banner_image
|
|
| 652 |
+ state = jingle.state
|
|
| 638 | 653 |
if state == self.JINGLE_STATE_NULL:
|
| 639 | 654 |
banner_image.hide()
|
| 640 | 655 |
else:
|
| ... | ... | @@ -656,7 +671,7 @@ class ChatControl(ChatControlBase): |
| 656 | 671 |
def update_audio(self):
|
| 657 | 672 |
self._update_jingle('audio')
|
| 658 | 673 |
hbox = self.xml.audio_buttons_hbox
|
| 659 |
- if self.audio_state == self.JINGLE_STATE_CONNECTED:
|
|
| 674 |
+ if self.jingle['audio'].state == self.JINGLE_STATE_CONNECTED:
|
|
| 660 | 675 |
# Set volume from config
|
| 661 | 676 |
input_vol = app.config.get('audio_input_volume')
|
| 662 | 677 |
output_vol = app.config.get('audio_output_volume')
|
| ... | ... | @@ -667,7 +682,7 @@ class ChatControl(ChatControlBase): |
| 667 | 682 |
# Show vbox
|
| 668 | 683 |
hbox.set_no_show_all(False)
|
| 669 | 684 |
hbox.show_all()
|
| 670 |
- elif not self.audio_sid:
|
|
| 685 |
+ elif not self.jingle['audio'].sid:
|
|
| 671 | 686 |
hbox.set_no_show_all(True)
|
| 672 | 687 |
hbox.hide()
|
| 673 | 688 |
|
| ... | ... | @@ -687,15 +702,17 @@ class ChatControl(ChatControlBase): |
| 687 | 702 |
# update MessageWindow._controls
|
| 688 | 703 |
self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
|
| 689 | 704 |
|
| 690 |
- def stop_jingle(self, sid=None, _reason=None):
|
|
| 691 |
- if self.audio_sid and sid in (self.audio_sid, None):
|
|
| 705 |
+ def stop_jingle(self, sid=None, reason=None):
|
|
| 706 |
+ audio_sid = self.jingle['audio'].sid
|
|
| 707 |
+ video_sid = self.jingle['video'].sid
|
|
| 708 |
+ if audio_sid and sid in (audio_sid, None):
|
|
| 692 | 709 |
self.close_jingle_content('audio')
|
| 693 |
- if self.video_sid and sid in (self.video_sid, None):
|
|
| 710 |
+ if video_sid and sid in (video_sid, None):
|
|
| 694 | 711 |
self.close_jingle_content('video')
|
| 695 | 712 |
|
| 696 |
- def _set_jingle_state(self, jingle_type, state, sid=None, reason=None):
|
|
| 697 |
- if jingle_type not in ('audio', 'video'):
|
|
| 698 |
- return
|
|
| 713 |
+ def _set_jingle_state(self, jingle_type: str, state: str, sid: str = None,
|
|
| 714 |
+ reason: str = None) -> None:
|
|
| 715 |
+ jingle = self.jingle[jingle_type]
|
|
| 699 | 716 |
if state in ('connecting', 'connected', 'stop', 'error') and reason:
|
| 700 | 717 |
info = _('%(type)s state : %(state)s, reason: %(reason)s') % {
|
| 701 | 718 |
'type': jingle_type.capitalize(),
|
| ... | ... | @@ -710,25 +727,25 @@ class ChatControl(ChatControlBase): |
| 710 | 727 |
'error': self.JINGLE_STATE_ERROR}
|
| 711 | 728 |
|
| 712 | 729 |
jingle_state = states[state]
|
| 713 |
- if (getattr(self, jingle_type + '_state') == jingle_state or
|
|
| 714 |
- state == 'error'):
|
|
| 730 |
+ if jingle.state == jingle_state or state == 'error':
|
|
| 715 | 731 |
return
|
| 716 | 732 |
|
| 717 |
- if (state == 'stop' and
|
|
| 718 |
- getattr(self, jingle_type + '_sid') not in (None, sid)):
|
|
| 733 |
+ if (state == 'stop' and jingle.sid not in (None, sid)):
|
|
| 719 | 734 |
return
|
| 720 | 735 |
|
| 721 |
- setattr(self, jingle_type + '_state', jingle_state)
|
|
| 722 |
- |
|
| 736 |
+ new_sid = None
|
|
| 723 | 737 |
if jingle_state == self.JINGLE_STATE_NULL:
|
| 724 |
- setattr(self, jingle_type + '_sid', None)
|
|
| 738 |
+ new_sid = None
|
|
| 725 | 739 |
if state in ('connection_received', 'connecting'):
|
| 726 |
- setattr(self, jingle_type + '_sid', sid)
|
|
| 740 |
+ new_sid = sid
|
|
| 741 |
+ |
|
| 742 |
+ jingle.state = jingle_state
|
|
| 743 |
+ jingle.sid = new_sid
|
|
| 727 | 744 |
|
| 728 | 745 |
var = GLib.Variant.new_boolean(jingle_state != self.JINGLE_STATE_NULL)
|
| 729 |
- getattr(self, jingle_type + '_action').change_state(var)
|
|
| 746 |
+ jingle.action.change_state(var)
|
|
| 730 | 747 |
|
| 731 |
- getattr(self, 'update_' + jingle_type)()
|
|
| 748 |
+ jingle.update()
|
|
| 732 | 749 |
|
| 733 | 750 |
def set_audio_state(self, state, sid=None, reason=None):
|
| 734 | 751 |
self._set_jingle_state('audio', state, sid=sid, reason=reason)
|
| ... | ... | @@ -739,7 +756,7 @@ class ChatControl(ChatControlBase): |
| 739 | 756 |
def _get_audio_content(self):
|
| 740 | 757 |
con = app.connections[self.account]
|
| 741 | 758 |
session = con.get_module('Jingle').get_jingle_session(
|
| 742 |
- self.contact.get_full_jid(), self.audio_sid)
|
|
| 759 |
+ self.contact.get_full_jid(), self.jingle['audio'].sid)
|
|
| 743 | 760 |
return session.get_content('audio')
|
| 744 | 761 |
|
| 745 | 762 |
def on_num_button_pressed(self, _widget, num):
|
| ... | ... | @@ -864,28 +881,31 @@ class ChatControl(ChatControlBase): |
| 864 | 881 |
self.xml.banner_name_label.set_markup(label_text)
|
| 865 | 882 |
self.xml.banner_name_label.set_tooltip_text(label_tooltip)
|
| 866 | 883 |
|
| 867 |
- def close_jingle_content(self, jingle_type):
|
|
| 868 |
- sid = getattr(self, jingle_type + '_sid')
|
|
| 869 |
- if not sid:
|
|
| 884 |
+ def close_jingle_content(self, jingle_type: str) -> None:
|
|
| 885 |
+ jingle = self.jingle[jingle_type]
|
|
| 886 |
+ if not jingle.sid:
|
|
| 870 | 887 |
return
|
| 871 |
- setattr(self, jingle_type + '_sid', None)
|
|
| 872 |
- setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL)
|
|
| 888 |
+ |
|
| 889 |
+ jingle.sid = None
|
|
| 890 |
+ jingle.state = self.JINGLE_STATE_NULL
|
|
| 891 |
+ |
|
| 873 | 892 |
con = app.connections[self.account]
|
| 874 | 893 |
session = con.get_module('Jingle').get_jingle_session(
|
| 875 |
- self.contact.get_full_jid(), sid)
|
|
| 894 |
+ self.contact.get_full_jid(), jingle.sid)
|
|
| 876 | 895 |
if session:
|
| 877 | 896 |
content = session.get_content(jingle_type)
|
| 878 | 897 |
if content:
|
| 879 | 898 |
session.remove_content(content.creator, content.name)
|
| 880 | 899 |
var = GLib.Variant.new_boolean(False)
|
| 881 |
- getattr(self, jingle_type + '_action').change_state(var)
|
|
| 882 |
- getattr(self, 'update_' + jingle_type)()
|
|
| 900 |
+ |
|
| 901 |
+ jingle.action.change_state(var)
|
|
| 902 |
+ jingle.update()
|
|
| 883 | 903 |
|
| 884 | 904 |
def on_jingle_button_toggled(self, state, jingle_type):
|
| 885 | 905 |
con = app.connections[self.account]
|
| 886 | 906 |
if state:
|
| 887 |
- if getattr(self, jingle_type + '_state') == \
|
|
| 888 |
- self.JINGLE_STATE_NULL:
|
|
| 907 |
+ if self.jingle[jingle_type].state == self.JINGLE_STATE_NULL:
|
|
| 908 |
+ con = app.connections[self.account]
|
|
| 889 | 909 |
if jingle_type == 'video':
|
| 890 | 910 |
video_hbox = self.xml.video_hbox
|
| 891 | 911 |
video_hbox.set_no_show_all(False)
|
| ... | ... | @@ -893,26 +913,15 @@ class ChatControl(ChatControlBase): |
| 893 | 913 |
fixed = self.xml.outgoing_fixed
|
| 894 | 914 |
fixed.set_no_show_all(False)
|
| 895 | 915 |
video_hbox.show_all()
|
| 896 |
- out_da = self.xml.outgoing_drawingarea
|
|
| 897 |
- out_da.realize()
|
|
| 898 |
- if os.name == 'nt':
|
|
| 899 |
- out_xid = out_da.get_window().handle
|
|
| 900 |
- else:
|
|
| 901 |
- out_xid = out_da.get_window().get_xid()
|
|
| 902 |
- else:
|
|
| 903 |
- out_xid = None
|
|
| 904 | 916 |
video_hbox.show_all()
|
| 905 |
- in_da = self.xml.incoming_drawingarea
|
|
| 906 |
- in_da.realize()
|
|
| 907 |
- in_xid = in_da.get_window().get_xid()
|
|
| 908 | 917 |
sid = con.get_module('Jingle').start_video(
|
| 909 |
- self.contact.get_full_jid(), in_xid, out_xid)
|
|
| 918 |
+ self.contact.get_full_jid())
|
|
| 910 | 919 |
|
| 911 | 920 |
else:
|
| 912 | 921 |
sid = con.get_module('Jingle').start_audio(
|
| 913 | 922 |
self.contact.get_full_jid())
|
| 914 | 923 |
|
| 915 |
- getattr(self, 'set_' + jingle_type + '_state')('connecting', sid)
|
|
| 924 |
+ self.jingle[jingle_type].set_state('connecting', sid)
|
|
| 916 | 925 |
else:
|
| 917 | 926 |
video_hbox = self.xml.video_hbox
|
| 918 | 927 |
video_hbox.set_no_show_all(True)
|
| ... | ... | @@ -412,11 +412,8 @@ class JingleAudio(JingleRTPContent): |
| 412 | 412 |
|
| 413 | 413 |
|
| 414 | 414 |
class JingleVideo(JingleRTPContent):
|
| 415 |
- def __init__(self, session, transport=None, in_xid=0, out_xid=0):
|
|
| 415 |
+ def __init__(self, session, transport=None):
|
|
| 416 | 416 |
JingleRTPContent.__init__(self, session, 'video', transport)
|
| 417 |
- self.in_xid = in_xid
|
|
| 418 |
- self.out_xid = out_xid
|
|
| 419 |
- self.out_xid_set = False
|
|
| 420 | 417 |
self.setup_stream()
|
| 421 | 418 |
|
| 422 | 419 |
def setup_stream(self):
|
| ... | ... | @@ -426,42 +423,24 @@ class JingleVideo(JingleRTPContent): |
| 426 | 423 |
JingleRTPContent.setup_stream(self, self._on_src_pad_added)
|
| 427 | 424 |
bus = self.pipeline.get_bus()
|
| 428 | 425 |
bus.enable_sync_message_emission()
|
| 429 |
- bus.connect('sync-message::element', self._on_sync_message)
|
|
| 430 | 426 |
|
| 431 |
- # the local parts
|
|
| 432 |
- if app.config.get('video_framerate'):
|
|
| 433 |
- framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
|
|
| 434 |
- app.config.get('video_framerate')
|
|
| 435 |
- else:
|
|
| 436 |
- framerate = ''
|
|
| 437 |
- try:
|
|
| 438 |
- w, h = app.config.get('video_size').split('x')
|
|
| 439 |
- except Exception:
|
|
| 440 |
- w = h = None
|
|
| 441 |
- if w and h:
|
|
| 442 |
- video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
|
|
| 443 |
- else:
|
|
| 444 |
- video_size = ''
|
|
| 427 |
+ def do_setup(self, self_display_sink, other_sink):
|
|
| 445 | 428 |
if app.config.get('video_see_self'):
|
| 446 |
- tee = '! tee name=t ! queue ! videoscale ! ' + \
|
|
| 447 |
- 'video/x-raw,width=160,height=120 ! videoconvert ! ' + \
|
|
| 448 |
- '%s t. ! queue ' % app.config.get(
|
|
| 449 |
- 'video_output_device')
|
|
| 429 |
+ tee = '! tee name=split ! queue name=self-display-queue split. ! queue name=network-queue'
|
|
| 450 | 430 |
else:
|
| 451 | 431 |
tee = ''
|
| 452 | 432 |
|
| 453 | 433 |
self.src_bin = self.make_bin_from_config('video_input_device',
|
| 454 |
- '%%s %s! %svideoscale ! %svideoconvert' %
|
|
| 455 |
- (tee, framerate, video_size),
|
|
| 434 |
+ '%%s %s' % tee,
|
|
| 456 | 435 |
_("video input"))
|
| 457 |
- |
|
| 458 | 436 |
self.pipeline.add(self.src_bin)
|
| 437 |
+ if app.config.get('video_see_self'):
|
|
| 438 |
+ self.pipeline.add(self_display_sink)
|
|
| 439 |
+ self_display_queue = self.src_bin.get_by_name('self-display-queue')
|
|
| 440 |
+ self_display_queue.get_static_pad('src').link_maybe_ghosting(self_display_sink.get_static_pad('sink'))
|
|
| 459 | 441 |
self.pipeline.set_state(Gst.State.PLAYING)
|
| 460 | 442 |
|
| 461 |
- self.sink = self.make_bin_from_config('video_output_device',
|
|
| 462 |
- 'videoscale ! videoconvert ! %s',
|
|
| 463 |
- _("video output"))
|
|
| 464 |
- |
|
| 443 |
+ self.sink = other_sink
|
|
| 465 | 444 |
self.pipeline.add(self.sink)
|
| 466 | 445 |
|
| 467 | 446 |
self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
|
| ... | ... | @@ -469,28 +448,13 @@ class JingleVideo(JingleRTPContent): |
| 469 | 448 |
|
| 470 | 449 |
# The following is needed for farstream to process ICE requests:
|
| 471 | 450 |
self.pipeline.set_state(Gst.State.PLAYING)
|
| 472 |
- |
|
| 473 |
- def _on_sync_message(self, bus, message):
|
|
| 474 |
- if message.get_structure() is None:
|
|
| 475 |
- return False
|
|
| 476 |
- if message.get_structure().get_name() == 'prepare-window-handle':
|
|
| 477 |
- message.src.set_property('force-aspect-ratio', True)
|
|
| 478 |
- imagesink = message.src
|
|
| 479 |
- if app.config.get('video_see_self') and not self.out_xid_set:
|
|
| 480 |
- imagesink.set_window_handle(self.out_xid)
|
|
| 481 |
- self.out_xid_set = True
|
|
| 482 |
- else:
|
|
| 483 |
- imagesink.set_window_handle(self.in_xid)
|
|
| 451 |
+ Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'video-graph')
|
|
| 484 | 452 |
|
| 485 | 453 |
def get_fallback_src(self):
|
| 486 | 454 |
# TODO: Use avatar?
|
| 487 | 455 |
pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert'
|
| 488 | 456 |
return Gst.parse_bin_from_description(pipeline, True)
|
| 489 | 457 |
|
| 490 |
- def destroy(self):
|
|
| 491 |
- JingleRTPContent.destroy(self)
|
|
| 492 |
- self.pipeline.get_bus().disconnect_by_func(self._on_sync_message)
|
|
| 493 |
- |
|
| 494 | 458 |
def get_content(desc):
|
| 495 | 459 |
if desc['media'] == 'audio':
|
| 496 | 460 |
return JingleAudio
|
| ... | ... | @@ -165,18 +165,18 @@ class Jingle(BaseModule): |
| 165 | 165 |
jingle.start_session()
|
| 166 | 166 |
return jingle.sid
|
| 167 | 167 |
|
| 168 |
- def start_video(self, jid, in_xid, out_xid):
|
|
| 168 |
+ def start_video(self, jid):
|
|
| 169 | 169 |
if self.get_jingle_session(jid, media='video'):
|
| 170 | 170 |
return self.get_jingle_session(jid, media='video').sid
|
| 171 | 171 |
jingle = self.get_jingle_session(jid, media='audio')
|
| 172 | 172 |
if jingle:
|
| 173 |
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
|
|
| 174 |
- out_xid=out_xid))
|
|
| 173 |
+ video = JingleVideo(jingle)
|
|
| 174 |
+ jingle.add_content('video', video)
|
|
| 175 | 175 |
else:
|
| 176 | 176 |
jingle = JingleSession(self._con, weinitiate=True, jid=jid)
|
| 177 | 177 |
self._sessions[jingle.sid] = jingle
|
| 178 |
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
|
|
| 179 |
- out_xid=out_xid))
|
|
| 178 |
+ video = JingleVideo(jingle)
|
|
| 179 |
+ jingle.add_content('video', video)
|
|
| 180 | 180 |
jingle.start_session()
|
| 181 | 181 |
return jingle.sid
|
| 182 | 182 |
|
| ... | ... | @@ -942,16 +942,13 @@ microphone-sensitivity-high-symbolic</property> |
| 942 | 942 |
<property name="can_focus">False</property>
|
| 943 | 943 |
<property name="no_show_all">True</property>
|
| 944 | 944 |
<child>
|
| 945 |
- <object class="GtkViewport" id="viewport1">
|
|
| 945 |
+ <object class="GtkViewport" id="outgoing_viewport">
|
|
| 946 | 946 |
<property name="width_request">160</property>
|
| 947 | 947 |
<property name="height_request">120</property>
|
| 948 | 948 |
<property name="visible">True</property>
|
| 949 | 949 |
<property name="can_focus">False</property>
|
| 950 | 950 |
<child>
|
| 951 |
- <object class="GtkDrawingArea" id="outgoing_drawingarea">
|
|
| 952 |
- <property name="visible">True</property>
|
|
| 953 |
- <property name="can_focus">False</property>
|
|
| 954 |
- </object>
|
|
| 951 |
+ <placeholder/>
|
|
| 955 | 952 |
</child>
|
| 956 | 953 |
</object>
|
| 957 | 954 |
</child>
|
| ... | ... | @@ -968,16 +965,11 @@ microphone-sensitivity-high-symbolic</property> |
| 968 | 965 |
<property name="can_focus">False</property>
|
| 969 | 966 |
<property name="orientation">vertical</property>
|
| 970 | 967 |
<child>
|
| 971 |
- <object class="GtkViewport" id="viewport2">
|
|
| 968 |
+ <object class="GtkViewport" id="incoming_viewport">
|
|
| 972 | 969 |
<property name="visible">True</property>
|
| 973 | 970 |
<property name="can_focus">False</property>
|
| 974 | 971 |
<child>
|
| 975 |
- <object class="GtkDrawingArea" id="incoming_drawingarea">
|
|
| 976 |
- <property name="width_request">320</property>
|
|
| 977 |
- <property name="height_request">240</property>
|
|
| 978 |
- <property name="visible">True</property>
|
|
| 979 |
- <property name="can_focus">False</property>
|
|
| 980 |
- </object>
|
|
| 972 |
+ <placeholder/>
|
|
| 981 | 973 |
</child>
|
| 982 | 974 |
</object>
|
| 983 | 975 |
<packing>
|
| ... | ... | @@ -29,7 +29,6 @@ from typing import Dict # pylint: disable=unused-import |
| 29 | 29 |
from typing import List # pylint: disable=unused-import
|
| 30 | 30 |
from typing import Tuple # pylint: disable=unused-import
|
| 31 | 31 |
|
| 32 |
-import os
|
|
| 33 | 32 |
import uuid
|
| 34 | 33 |
import logging
|
| 35 | 34 |
|
| ... | ... | @@ -54,6 +53,7 @@ from gajim.gtk.util import get_icon_name |
| 54 | 53 |
from gajim.gtk.util import get_builder
|
| 55 | 54 |
from gajim.gtk.util import get_activity_icon_name
|
| 56 | 55 |
from gajim.gtk.util import get_app_window
|
| 56 |
+from gajim.gtk import gstreamer
|
|
| 57 | 57 |
|
| 58 | 58 |
if app.is_installed('GSPELL'):
|
| 59 | 59 |
from gi.repository import Gspell # pylint: disable=ungrouped-imports
|
| ... | ... | @@ -1100,34 +1100,12 @@ class VoIPCallReceivedDialog: |
| 1100 | 1100 |
fixed = ctrl.xml.get_object('outgoing_fixed')
|
| 1101 | 1101 |
fixed.set_no_show_all(False)
|
| 1102 | 1102 |
video_hbox.show_all()
|
| 1103 |
- ctrl.xml.get_object('incoming_drawingarea').realize()
|
|
| 1104 |
- if os.name == 'nt':
|
|
| 1105 |
- in_xid = ctrl.xml.get_object('incoming_drawingarea').\
|
|
| 1106 |
- get_window().handle
|
|
| 1107 |
- else:
|
|
| 1108 |
- in_xid = ctrl.xml.get_object('incoming_drawingarea').\
|
|
| 1109 |
- get_property('window').get_xid()
|
|
| 1110 | 1103 |
content = session.get_content('video')
|
| 1111 |
- # move outgoing stream to chat window
|
|
| 1112 |
- if app.config.get('video_see_self'):
|
|
| 1113 |
- ctrl.xml.get_object('outgoing_drawingarea').realize()
|
|
| 1114 |
- if os.name == 'nt':
|
|
| 1115 |
- out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
|
|
| 1116 |
- get_window().handle
|
|
| 1117 |
- else:
|
|
| 1118 |
- out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
|
|
| 1119 |
- get_property('window').get_xid()
|
|
| 1120 |
- b = content.src_bin
|
|
| 1121 |
- for e in b.children:
|
|
| 1122 |
- if not e.get_name().startswith('autovideosink'):
|
|
| 1123 |
- continue
|
|
| 1124 |
- for f in e.children:
|
|
| 1125 |
- if f.get_name().startswith('autovideosink'):
|
|
| 1126 |
- f.set_window_handle(out_xid)
|
|
| 1127 |
- content.out_xid = out_xid
|
|
| 1128 |
- break
|
|
| 1129 |
- break
|
|
| 1130 |
- content.in_xid = in_xid
|
|
| 1104 |
+ sink_other, widget_other, _ = gstreamer.create_gtk_widget()
|
|
| 1105 |
+ sink_self, widget_self, _ = gstreamer.create_gtk_widget()
|
|
| 1106 |
+ ctrl.xml.incoming_viewport.add(widget_other)
|
|
| 1107 |
+ ctrl.xml.outgoing_viewport.add(widget_self)
|
|
| 1108 |
+ content.do_setup(sink_self, sink_other)
|
|
| 1131 | 1109 |
ctrl.set_video_state('connecting', self.sid)
|
| 1132 | 1110 |
# Now, accept the content/sessions.
|
| 1133 | 1111 |
# This should be done after the chat control is running
|
| 1 |
+from typing import Tuple, Optional
|
|
| 2 |
+ |
|
| 3 |
+from gi.repository import Gst
|
|
| 4 |
+from gi.repository import Gtk
|
|
| 5 |
+ |
|
| 6 |
+def create_gtk_widget() -> Tuple[Optional[Gst.Element], Optional[Gtk.Widget], Optional[str]]:
|
|
| 7 |
+ gtkglsink = Gst.ElementFactory.make('gtkglsink', None)
|
|
| 8 |
+ if gtkglsink is not None:
|
|
| 9 |
+ glsinkbin = Gst.ElementFactory.make('glsinkbin', None)
|
|
| 10 |
+ if glsinkbin is None:
|
|
| 11 |
+ return None, None, None
|
|
| 12 |
+ glsinkbin.set_property('sink', gtkglsink)
|
|
| 13 |
+ sink = glsinkbin
|
|
| 14 |
+ widget = gtkglsink.get_property('widget')
|
|
| 15 |
+ name = 'gtkglsink'
|
|
| 16 |
+ else:
|
|
| 17 |
+ sink = Gst.ElementFactory.make('gtksink', None)
|
|
| 18 |
+ if sink is None:
|
|
| 19 |
+ return None, None, None
|
|
| 20 |
+ widget = sink.get_property('widget')
|
|
| 21 |
+ name = 'gtksink'
|
|
| 22 |
+ widget.set_visible(True)
|
|
| 23 |
+ widget.set_property('expand', True)
|
|
| 24 |
+ return sink, widget, name
|
| ... | ... | @@ -38,6 +38,7 @@ from gajim.gtk.util import open_window |
| 38 | 38 |
from gajim.gtk.dialogs import AspellDictError
|
| 39 | 39 |
from gajim.gtk.sounds import ManageSounds
|
| 40 | 40 |
from gajim.gtk.const import ControlType
|
| 41 |
+from gajim.gtk import gstreamer
|
|
| 41 | 42 |
|
| 42 | 43 |
try:
|
| 43 | 44 |
from gajim.common.multimedia_helpers import AudioInputManager, AudioOutputManager
|
| ... | ... | @@ -384,7 +385,7 @@ class Preferences(Gtk.ApplicationWindow): |
| 384 | 385 |
|
| 385 | 386 |
def on_av_map(tab):
|
| 386 | 387 |
label = self._ui.selected_video_output
|
| 387 |
- sink, widget, name = self.setup_video_output()
|
|
| 388 |
+ sink, widget, name = gstreamer.create_gtk_widget()
|
|
| 388 | 389 |
if sink is None:
|
| 389 | 390 |
log.error('Failed to obtain a working Gstreamer GTK+ sink, '
|
| 390 | 391 |
'video support will be disabled')
|
| ... | ... | @@ -508,27 +509,6 @@ class Preferences(Gtk.ApplicationWindow): |
| 508 | 509 |
if event.keyval == Gdk.KEY_Escape:
|
| 509 | 510 |
self.destroy()
|
| 510 | 511 |
|
| 511 |
- @staticmethod
|
|
| 512 |
- def setup_video_output():
|
|
| 513 |
- gtkglsink = Gst.ElementFactory.make('gtkglsink', None)
|
|
| 514 |
- if gtkglsink is not None:
|
|
| 515 |
- glsinkbin = Gst.ElementFactory.make('glsinkbin', None)
|
|
| 516 |
- if glsinkbin is None:
|
|
| 517 |
- return None, None, None
|
|
| 518 |
- glsinkbin.set_property('sink', gtkglsink)
|
|
| 519 |
- sink = glsinkbin
|
|
| 520 |
- widget = gtkglsink.get_property('widget')
|
|
| 521 |
- name = 'gtkglsink'
|
|
| 522 |
- else:
|
|
| 523 |
- sink = Gst.ElementFactory.make('gtksink', None)
|
|
| 524 |
- if sink is None:
|
|
| 525 |
- return None, None, None
|
|
| 526 |
- widget = sink.get_property('widget')
|
|
| 527 |
- name = 'gtksink'
|
|
| 528 |
- widget.set_visible(True)
|
|
| 529 |
- widget.set_property('expand', True)
|
|
| 530 |
- return sink, widget, name
|
|
| 531 |
- |
|
| 532 | 512 |
def get_per_account_option(self, opt):
|
| 533 | 513 |
"""
|
| 534 | 514 |
Return the value of the option opt if it's the same in all accounts else
|
| ... | ... | @@ -1024,7 +1024,7 @@ class Interface: |
| 1024 | 1024 |
account = obj.conn.name
|
| 1025 | 1025 |
ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
|
| 1026 | 1026 |
or self.msg_win_mgr.get_control(obj.jid, account))
|
| 1027 |
- if ctrl and obj.sid == ctrl.audio_sid:
|
|
| 1027 |
+ if ctrl and obj.sid == ctrl.jingle['audio'].sid:
|
|
| 1028 | 1028 |
ctrl.set_audio_state('error', reason=obj.reason)
|
| 1029 | 1029 |
|
| 1030 | 1030 |
@staticmethod
|