Add support for network streams.

This commit is contained in:
Tomas Groth 2020-03-08 22:05:09 +00:00 committed by Tim Bentley
parent b51829d4d6
commit 127ee05ac3
9 changed files with 329 additions and 124 deletions

View File

@ -37,6 +37,7 @@ from openlp.core.ui.media.vlcplayer import get_vlc
if get_vlc() is not None: if get_vlc() is not None:
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
from openlp.plugins.media.forms.networkstreamselectorform import NetworkStreamSelectorForm
class BackgroundPage(GridLayoutPage): class BackgroundPage(GridLayoutPage):
@ -130,11 +131,16 @@ class BackgroundPage(GridLayoutPage):
self.stream_lineedit.setObjectName('stream_lineedit') self.stream_lineedit.setObjectName('stream_lineedit')
self.stream_lineedit.setReadOnly(True) self.stream_lineedit.setReadOnly(True)
self.stream_layout.addWidget(self.stream_lineedit) self.stream_layout.addWidget(self.stream_lineedit)
# button to open select stream forms # button to open select device stream form
self.stream_select_button = QtWidgets.QToolButton(self) self.device_stream_select_button = QtWidgets.QToolButton(self)
self.stream_select_button.setObjectName('stream_select_button') self.device_stream_select_button.setObjectName('device_stream_select_button')
self.stream_select_button.setIcon(UiIcons().device_stream) self.device_stream_select_button.setIcon(UiIcons().device_stream)
self.stream_layout.addWidget(self.stream_select_button) self.stream_layout.addWidget(self.device_stream_select_button)
# button to open select network stream form
self.network_stream_select_button = QtWidgets.QToolButton(self)
self.network_stream_select_button.setObjectName('network_stream_select_button')
self.network_stream_select_button.setIcon(UiIcons().network_stream)
self.stream_layout.addWidget(self.network_stream_select_button)
self.layout.addLayout(self.stream_layout, 6, 1, 1, 3) self.layout.addLayout(self.stream_layout, 6, 1, 1, 3)
self.stream_color_label = FormLabel(self) self.stream_color_label = FormLabel(self)
self.stream_color_label.setObjectName('stream_color_label') self.stream_color_label.setObjectName('stream_color_label')
@ -143,14 +149,15 @@ class BackgroundPage(GridLayoutPage):
self.stream_color_button.color = '#000000' self.stream_color_button.color = '#000000'
self.stream_color_button.setObjectName('stream_color_button') self.stream_color_button.setObjectName('stream_color_button')
self.layout.addWidget(self.stream_color_button, 7, 1) self.layout.addWidget(self.stream_color_button, 7, 1)
self.stream_widgets = [self.stream_label, self.stream_lineedit, self.stream_select_button, self.stream_widgets = [self.stream_label, self.stream_lineedit, self.device_stream_select_button,
self.stream_color_label, self.stream_color_button] self.stream_color_label, self.stream_color_button]
# Force everything up # Force everything up
self.layout_spacer = QtWidgets.QSpacerItem(1, 1) self.layout_spacer = QtWidgets.QSpacerItem(1, 1)
self.layout.addItem(self.layout_spacer, 8, 0, 1, 4) self.layout.addItem(self.layout_spacer, 8, 0, 1, 4)
# Connect slots # Connect slots
self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed) self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed)
self.stream_select_button.clicked.connect(self._on_stream_select_button_triggered) self.device_stream_select_button.clicked.connect(self._on_device_stream_select_button_triggered)
self.network_stream_select_button.clicked.connect(self._on_network_stream_select_button_triggered)
# Force the first set of widgets to show # Force the first set of widgets to show
self._on_background_type_index_changed(0) self._on_background_type_index_changed(0)
@ -208,7 +215,7 @@ class BackgroundPage(GridLayoutPage):
for widget in widget_sets[index]: for widget in widget_sets[index]:
widget.show() widget.show()
def _on_stream_select_button_triggered(self): def _on_device_stream_select_button_triggered(self):
""" """
Open the Stream selection form. Open the Stream selection form.
""" """
@ -222,6 +229,20 @@ class BackgroundPage(GridLayoutPage):
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'), critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
translate('MediaPlugin.MediaItem', 'Device streaming support requires VLC.')) translate('MediaPlugin.MediaItem', 'Device streaming support requires VLC.'))
def _on_network_stream_select_button_triggered(self):
"""
Open the Stream selection form.
"""
if get_vlc():
stream_selector_form = NetworkStreamSelectorForm(self, self.set_stream, True)
if self.stream_lineedit.text():
stream_selector_form.set_mrl(self.stream_lineedit.text())
stream_selector_form.exec()
del stream_selector_form
else:
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
translate('MediaPlugin.MediaItem', 'Network streaming support requires VLC.'))
def set_stream(self, stream_str): def set_stream(self, stream_str):
""" """
callback method used to get the stream mrl and options callback method used to get the stream mrl and options

View File

@ -99,6 +99,7 @@ class UiIcons(metaclass=Singleton):
'media': {'icon': 'fa.fax'}, 'media': {'icon': 'fa.fax'},
'minus': {'icon': 'fa.minus'}, 'minus': {'icon': 'fa.minus'},
'music': {'icon': 'fa.music'}, 'music': {'icon': 'fa.music'},
'network_stream': {'icon': 'fa.link'},
'new': {'icon': 'fa.file'}, 'new': {'icon': 'fa.file'},
'new_group': {'icon': 'fa.folder'}, 'new_group': {'icon': 'fa.folder'},
'notes': {'icon': 'fa.sticky-note'}, 'notes': {'icon': 'fa.sticky-note'},

View File

@ -104,16 +104,18 @@ def parse_optical_path(input_string):
return filename, title, audio_track, subtitle_track, start, end, clip_name return filename, title, audio_track, subtitle_track, start, end, clip_name
def parse_devicestream_path(input_string): def parse_stream_path(input_string):
""" """
Split the device stream path info. Split the device stream path info.
:param input_string: The string to parse :param input_string: The string to parse
:return: The elements extracted from the string: streamname, MRL, VLC-options :return: The elements extracted from the string: streamname, MRL, VLC-options
""" """
log.debug('parse_devicestream_path, about to parse: "{text}"'.format(text=input_string)) log.debug('parse_stream_path, about to parse: "{text}"'.format(text=input_string))
# skip the header: 'devicestream:', split at '&&' # skip the header: 'devicestream:' or 'networkstream:'
stream_info = input_string[len('devicestream:'):].split('&&') header, data = input_string.split(':', 1)
# split at '&&'
stream_info = data.split('&&')
name = stream_info[0] name = stream_info[0]
mrl = stream_info[1] mrl = stream_info[1]
options = stream_info[2] options = stream_info[2]

View File

@ -41,7 +41,7 @@ from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType, HideMode from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_devicestream_path, \ from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path, \
VIDEO_EXT, AUDIO_EXT VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media.remote import register_views from openlp.core.ui.media.remote import register_views
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
@ -231,7 +231,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.file_info = service_item.background_audio controller.media_info.file_info = service_item.background_audio
else: else:
if service_item.is_capable(ItemCapabilities.HasBackgroundStream): if service_item.is_capable(ItemCapabilities.HasBackgroundStream):
(name, mrl, options) = parse_devicestream_path(service_item.stream_mrl) (name, mrl, options) = parse_stream_path(service_item.stream_mrl)
controller.media_info.file_info = (mrl, options) controller.media_info.file_info = (mrl, options)
controller.media_info.is_background = True controller.media_info.is_background = True
controller.media_info.media_type = MediaType.Stream controller.media_info.media_type = MediaType.Stream
@ -255,7 +255,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
log.debug('video is stream and live') log.debug('video is stream and live')
path = service_item.get_frames()[0]['path'] path = service_item.get_frames()[0]['path']
controller.media_info.media_type = MediaType.Stream controller.media_info.media_type = MediaType.Stream
(name, mrl, options) = parse_devicestream_path(path) (name, mrl, options) = parse_stream_path(path)
controller.media_info.file_info = (mrl, options) controller.media_info.file_info = (mrl, options)
is_valid = self._check_file_type(controller, display) is_valid = self._check_file_type(controller, display)
else: else:
@ -274,7 +274,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
elif service_item.is_capable(ItemCapabilities.CanStream): elif service_item.is_capable(ItemCapabilities.CanStream):
path = service_item.get_frames()[0]['path'] path = service_item.get_frames()[0]['path']
controller.media_info.media_type = MediaType.Stream controller.media_info.media_type = MediaType.Stream
(name, mrl, options) = parse_devicestream_path(path) (name, mrl, options) = parse_stream_path(path)
controller.media_info.file_info = (mrl, options) controller.media_info.file_info = (mrl, options)
is_valid = self._check_file_type(controller, display) is_valid = self._check_file_type(controller, display)
else: else:

View File

@ -18,3 +18,149 @@
# You should have received a copy of the GNU General Public License # # You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. # # along with this program. If not, see <https://www.gnu.org/licenses/>. #
########################################################################## ##########################################################################
import re
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import parse_stream_path
class StreamSelectorFormBase(QtWidgets.QDialog):
"""
Class to manage the clip selection
"""
def __init__(self, parent, callback, theme_stream=False):
"""
Constructor
"""
super(StreamSelectorFormBase, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.callback = callback
self.theme_stream = theme_stream
self.setup_base_ui()
self.type = ''
def setup_base_ui(self):
self.setObjectName('stream_selector')
self.combobox_size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.Fixed)
self.setSizePolicy(
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.setObjectName('main_layout')
self.top_widget = QtWidgets.QWidget(self)
self.top_widget.setObjectName('top_widget')
self.top_layout = QtWidgets.QFormLayout(self.top_widget)
self.top_layout.setObjectName('top_layout')
# Stream name
if not self.theme_stream:
self.stream_name_label = QtWidgets.QLabel(self.top_widget)
self.stream_name_label.setObjectName('stream_name_label')
self.stream_name_edit = QtWidgets.QLineEdit(self.top_widget)
self.stream_name_edit.setObjectName('stream_name_edit')
self.top_layout.addRow(self.stream_name_label, self.stream_name_edit)
def exec(self):
"""
Start dialog
"""
return QtWidgets.QDialog.exec(self)
def accept(self):
"""
Saves the current stream as a clip to the mediamanager
"""
if not self.theme_stream:
# Verify that a stream name exists
if not self.stream_name_edit.text().strip():
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A Stream name is needed.'))
return
stream_name = self.stream_name_edit.text().strip()
else:
stream_name = ' '
# Verify that a MRL exists
if not self.more_options_group.mrl_lineedit.text().strip():
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A MRL is needed.'), parent=self)
return
stream_string = '{type}:{name}&&{mrl}&&{options}'.format(
type=self.type, name=stream_name, mrl=self.more_options_group.mrl_lineedit.text().strip(),
options=self.more_options_group.vlc_options_lineedit.text().strip())
self.callback(stream_string)
return QtWidgets.QDialog.accept(self)
def update_mrl_options(self, mrl, options):
"""
Callback method used to fill the MRL and Options text fields
"""
options += ' :live-caching={cache}'.format(cache=self.more_options_group.caching.value())
self.more_options_group.mrl_lineedit.setText(mrl)
self.more_options_group.vlc_options_lineedit.setText(options)
def set_mrl(self, stream_str):
"""
Setup the stream widgets based on the saved stream string. This is best effort as the string is
editable for the user.
"""
(name, mrl, options) = parse_stream_path(stream_str)
cache = re.search(r'live-caching=(\d+)', options)
if cache:
self.more_options_group.caching.setValue(int(cache.group(1)))
self.more_options_group.mrl_lineedit.setText(mrl)
self.more_options_group.vlc_options_lineedit.setText(options)
class VLCOptionsWidget(QtWidgets.QGroupBox):
"""
Groupbox widget for VLC options: caching, mrl and VLC options
"""
def __init__(self, parent=None):
"""
Initialise the widget.
:param QtWidgets.QWidget | None parent: The widgets parent
"""
super().__init__(parent)
self.setup_ui()
self.retranslate_ui()
def setup_ui(self):
"""
Create the widget layout and sub widgets
"""
# Groupbox for VLC options
self.vlc_options_group_layout = QtWidgets.QFormLayout(self)
self.vlc_options_group_layout.setObjectName('more_options_group_layout')
# Caching spinbox
self.caching_label = QtWidgets.QLabel(self)
self.caching_label.setObjectName('caching_label')
self.caching = QtWidgets.QSpinBox(self)
self.caching.setAlignment(QtCore.Qt.AlignRight)
self.caching.setSuffix(' ms')
self.caching.setSingleStep(100)
self.caching.setMaximum(65535)
self.caching.setValue(300)
self.vlc_options_group_layout.addRow(self.caching_label, self.caching)
# MRL
self.mrl_label = QtWidgets.QLabel(self)
self.mrl_label.setObjectName('mrl_label')
self.mrl_lineedit = QtWidgets.QLineEdit(self)
self.mrl_lineedit.setObjectName('mrl_lineedit')
self.vlc_options_group_layout.addRow(self.mrl_label, self.mrl_lineedit)
# VLC options
self.vlc_options_label = QtWidgets.QLabel(self)
self.vlc_options_label.setObjectName('vlc_options_label')
self.vlc_options_lineedit = QtWidgets.QLineEdit(self)
self.vlc_options_lineedit.setObjectName('vlc_options_lineedit')
self.vlc_options_group_layout.addRow(self.vlc_options_label, self.vlc_options_lineedit)
def retranslate_ui(self):
self.setTitle(translate('MediaPlugin.StreamSelector', 'More options'))
self.caching_label.setText(translate('MediaPlugin.StreamSelector', 'Caching'))
self.mrl_label.setText(translate('MediaPlugin.StreamSelector', 'MRL'))
self.vlc_options_label.setText(translate('MediaPlugin.StreamSelector', 'VLC options'))

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 OpenLP Developers #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
from PyQt5 import QtWidgets
from openlp.core.common.i18n import translate
from openlp.plugins.media.forms import StreamSelectorFormBase, VLCOptionsWidget
log = logging.getLogger(__name__)
class NetworkStreamSelectorForm(StreamSelectorFormBase):
"""
Class to manage the network stream selection
"""
log.info('{name} NetworkStreamSelectorForm loaded'.format(name=__name__))
def __init__(self, parent, callback, theme_stream=False):
"""
Constructor
"""
super(NetworkStreamSelectorForm, self).__init__(parent, callback, theme_stream)
self.type = 'networkstream'
self.setup_ui()
def setup_ui(self):
self.net_mrl_label = QtWidgets.QLabel(self)
self.net_mrl_label.setObjectName('net_mrl_label')
self.net_mrl_lineedit = QtWidgets.QLineEdit(self)
self.net_mrl_lineedit.setObjectName('net_mrl_lineedit')
self.top_layout.addRow(self.net_mrl_label, self.net_mrl_lineedit)
self.main_layout.addWidget(self.top_widget)
# Add groupbox for VLC options
self.more_options_group = VLCOptionsWidget(self)
# Add groupbox for more options to main layout
self.main_layout.addWidget(self.more_options_group)
# Save and close buttons
self.button_box = QtWidgets.QDialogButtonBox(self)
self.button_box.addButton(QtWidgets.QDialogButtonBox.Save)
self.button_box.addButton(QtWidgets.QDialogButtonBox.Close)
self.close_button = self.button_box.button(QtWidgets.QDialogButtonBox.Close)
self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save)
self.main_layout.addWidget(self.button_box)
# translate
self.retranslate_ui()
# connect
self.net_mrl_lineedit.editingFinished.connect(self.on_updates)
self.more_options_group.caching.valueChanged.connect(self.on_updates)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
def retranslate_ui(self):
self.setWindowTitle(translate('MediaPlugin.StreamSelector', 'Insert Input Stream'))
if not self.theme_stream:
self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name'))
self.net_mrl_label.setText(translate('MediaPlugin.StreamSelector', 'Network URL'))
def on_updates(self):
self.update_mrl_options(self.net_mrl_lineedit.text(), '')

View File

@ -34,6 +34,7 @@ from PyQt5.QtMultimedia import QCameraInfo, QAudioDeviceInfo, QAudio
from openlp.core.common import is_linux, is_macosx, is_win from openlp.core.common import is_linux, is_macosx, is_win
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.plugins.media.forms import VLCOptionsWidget
# Copied from VLC source code: modules/access/v4l2/v4l2.c # Copied from VLC source code: modules/access/v4l2/v4l2.c
VIDEO_STANDARDS_VLC = [ VIDEO_STANDARDS_VLC = [
@ -651,25 +652,6 @@ class CaptureVideoDirectShowWidget(CaptureVideoQtDetectWidget):
class Ui_StreamSelector(object): class Ui_StreamSelector(object):
def setup_ui(self, stream_selector): def setup_ui(self, stream_selector):
stream_selector.setObjectName('stream_selector')
self.combobox_size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.Fixed)
stream_selector.setSizePolicy(
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
self.main_layout = QtWidgets.QVBoxLayout(stream_selector)
self.main_layout.setObjectName('main_layout')
self.top_widget = QtWidgets.QWidget(stream_selector)
self.top_widget.setObjectName('top_widget')
self.top_layout = QtWidgets.QFormLayout(self.top_widget)
self.top_layout.setObjectName('top_layout')
# Stream name
if not self.theme_stream:
self.stream_name_label = QtWidgets.QLabel(self.top_widget)
self.stream_name_label.setObjectName('stream_name_label')
self.stream_name_edit = QtWidgets.QLineEdit(self.top_widget)
self.stream_name_edit.setObjectName('stream_name_edit')
self.top_layout.addRow(self.stream_name_label, self.stream_name_edit)
# Mode combobox # Mode combobox
self.capture_mode_label = QtWidgets.QLabel(self.top_widget) self.capture_mode_label = QtWidgets.QLabel(self.top_widget)
self.capture_mode_label.setObjectName('capture_mode_label') self.capture_mode_label.setObjectName('capture_mode_label')
@ -719,33 +701,8 @@ class Ui_StreamSelector(object):
for i in range(self.stacked_modes_layout.count()): for i in range(self.stacked_modes_layout.count()):
self.stacked_modes_layout.widget(i).find_devices() self.stacked_modes_layout.widget(i).find_devices()
self.stacked_modes_layout.widget(i).retranslate_ui() self.stacked_modes_layout.widget(i).retranslate_ui()
# Groupbox for more options # Add groupbox for VLC options
self.more_options_group = QtWidgets.QGroupBox(self) self.more_options_group = VLCOptionsWidget(self)
self.more_options_group.setObjectName('more_options_group')
self.more_options_group_layout = QtWidgets.QFormLayout(self.more_options_group)
self.more_options_group_layout.setObjectName('more_options_group_layout')
# Caching spinbox
self.caching_label = QtWidgets.QLabel(self)
self.caching_label.setObjectName('caching_label')
self.caching = QtWidgets.QSpinBox(self)
self.caching.setAlignment(QtCore.Qt.AlignRight)
self.caching.setSuffix(' ms')
self.caching.setSingleStep(100)
self.caching.setMaximum(65535)
self.caching.setValue(300)
self.more_options_group_layout.addRow(self.caching_label, self.caching)
# MRL
self.mrl_label = QtWidgets.QLabel(self)
self.mrl_label.setObjectName('mrl_label')
self.mrl_lineedit = QtWidgets.QLineEdit(self)
self.mrl_lineedit.setObjectName('mrl_lineedit')
self.more_options_group_layout.addRow(self.mrl_label, self.mrl_lineedit)
# VLC options
self.vlc_options_label = QtWidgets.QLabel(self)
self.vlc_options_label.setObjectName('vlc_options_label')
self.vlc_options_lineedit = QtWidgets.QLineEdit(self)
self.vlc_options_lineedit.setObjectName('vlc_options_lineedit')
self.more_options_group_layout.addRow(self.vlc_options_label, self.vlc_options_lineedit)
# Add groupbox for more options to main layout # Add groupbox for more options to main layout
self.main_layout.addWidget(self.more_options_group) self.main_layout.addWidget(self.more_options_group)
# Save and close buttons # Save and close buttons
@ -760,7 +717,7 @@ class Ui_StreamSelector(object):
self.retranslate_ui(stream_selector) self.retranslate_ui(stream_selector)
# connect # connect
self.capture_mode_combo_box.currentIndexChanged.connect(stream_selector.on_capture_mode_combo_box) self.capture_mode_combo_box.currentIndexChanged.connect(stream_selector.on_capture_mode_combo_box)
self.caching.valueChanged.connect(stream_selector.on_capture_mode_combo_box) self.more_options_group.caching.valueChanged.connect(stream_selector.on_capture_mode_combo_box)
self.button_box.accepted.connect(stream_selector.accept) self.button_box.accepted.connect(stream_selector.accept)
self.button_box.rejected.connect(stream_selector.reject) self.button_box.rejected.connect(stream_selector.reject)
@ -769,7 +726,3 @@ class Ui_StreamSelector(object):
if not self.theme_stream: if not self.theme_stream:
self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name')) self.stream_name_label.setText(translate('MediaPlugin.StreamSelector', 'Stream name'))
self.capture_mode_label.setText(translate('MediaPlugin.StreamSelector', 'Capture Mode')) self.capture_mode_label.setText(translate('MediaPlugin.StreamSelector', 'Capture Mode'))
self.more_options_group.setTitle(translate('MediaPlugin.StreamSelector', 'More options'))
self.caching_label.setText(translate('MediaPlugin.StreamSelector', 'Caching'))
self.mrl_label.setText(translate('MediaPlugin.StreamSelector', 'MRL'))
self.vlc_options_label.setText(translate('MediaPlugin.StreamSelector', 'VLC options'))

View File

@ -20,18 +20,16 @@
########################################################################## ##########################################################################
import logging import logging
import re
from PyQt5 import QtCore, QtWidgets
from openlp.plugins.media.forms.streamselectordialog import Ui_StreamSelector from openlp.plugins.media.forms.streamselectordialog import Ui_StreamSelector
from openlp.core.ui.media import parse_devicestream_path from openlp.core.ui.media import parse_stream_path
from openlp.core.lib.ui import critical_error_message_box from openlp.plugins.media.forms import StreamSelectorFormBase
from openlp.core.common.i18n import translate
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector): class StreamSelectorForm(StreamSelectorFormBase, Ui_StreamSelector):
""" """
Class to manage the clip selection Class to manage the clip selection
""" """
@ -41,53 +39,14 @@ class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector):
""" """
Constructor Constructor
""" """
super(StreamSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | super(StreamSelectorForm, self).__init__(parent, callback, theme_stream)
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint) self.type = 'devicestream'
self.callback = callback
self.theme_stream = theme_stream
self.setup_ui(self) self.setup_ui(self)
# setup callbacks # setup callbacks
for i in range(self.stacked_modes_layout.count()): for i in range(self.stacked_modes_layout.count()):
self.stacked_modes_layout.widget(i).set_callback(self.update_mrl_options) self.stacked_modes_layout.widget(i).set_callback(self.update_mrl_options)
self.stacked_modes_layout.currentWidget().update_mrl() self.stacked_modes_layout.currentWidget().update_mrl()
def exec(self):
"""
Start dialog
"""
return QtWidgets.QDialog.exec(self)
def accept(self):
"""
Saves the current stream as a clip to the mediamanager
"""
log.debug('in StreamSelectorForm.accept')
if not self.theme_stream:
# Verify that a stream name exists
if not self.stream_name_edit.text().strip():
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A Stream name is needed.'))
return
stream_name = self.stream_name_edit.text().strip()
else:
stream_name = ' '
# Verify that a MRL exists
if not self.mrl_lineedit.text().strip():
critical_error_message_box(message=translate('MediaPlugin.StreamSelector', 'A MRL is needed.'), parent=self)
return
stream_string = 'devicestream:{name}&&{mrl}&&{options}'.format(name=stream_name,
mrl=self.mrl_lineedit.text().strip(),
options=self.vlc_options_lineedit.text().strip())
self.callback(stream_string)
return QtWidgets.QDialog.accept(self)
def update_mrl_options(self, mrl, options):
"""
Callback method used to fill the MRL and Options text fields
"""
options += ' :live-caching={cache}'.format(cache=self.caching.value())
self.mrl_lineedit.setText(mrl)
self.vlc_options_lineedit.setText(options)
def on_capture_mode_combo_box(self): def on_capture_mode_combo_box(self):
self.stacked_modes_layout.setCurrentIndex(self.capture_mode_combo_box.currentIndex()) self.stacked_modes_layout.setCurrentIndex(self.capture_mode_combo_box.currentIndex())
self.stacked_modes_layout.currentWidget().update_mrl() self.stacked_modes_layout.currentWidget().update_mrl()
@ -97,12 +56,14 @@ class StreamSelectorForm(QtWidgets.QDialog, Ui_StreamSelector):
Setup the stream widgets based on the saved devicestream. This is best effort as the string is Setup the stream widgets based on the saved devicestream. This is best effort as the string is
editable for the user. editable for the user.
""" """
(name, mrl, options) = parse_devicestream_path(device_stream_str) (name, mrl, options) = parse_stream_path(device_stream_str)
for i in range(self.stacked_modes_layout.count()): for i in range(self.stacked_modes_layout.count()):
if self.stacked_modes_layout.widget(i).has_support_for_mrl(mrl, options): if self.stacked_modes_layout.widget(i).has_support_for_mrl(mrl, options):
self.stacked_modes_layout.setCurrentIndex(i) self.stacked_modes_layout.setCurrentIndex(i)
self.stacked_modes_layout.widget(i).set_mrl(mrl, options) self.stacked_modes_layout.widget(i).set_mrl(mrl, options)
break break
cache = re.search(r'live-caching=(\d+)', options)
self.mrl_lineedit.setText(mrl) if cache:
self.vlc_options_lineedit.setText(options) self.more_options_group.caching.setValue(int(cache.group(1)))
self.more_options_group.mrl_lineedit.setText(mrl)
self.more_options_group.vlc_options_lineedit.setText(options)

View File

@ -35,12 +35,13 @@ from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.state import State from openlp.core.state import State
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.core.ui.media import parse_optical_path, parse_devicestream_path, format_milliseconds, AUDIO_EXT, VIDEO_EXT from openlp.core.ui.media import parse_optical_path, parse_stream_path, format_milliseconds, AUDIO_EXT, VIDEO_EXT
from openlp.core.ui.media.vlcplayer import get_vlc from openlp.core.ui.media.vlcplayer import get_vlc
if get_vlc() is not None: if get_vlc() is not None:
from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm
from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm from openlp.plugins.media.forms.streamselectorform import StreamSelectorForm
from openlp.plugins.media.forms.networkstreamselectorform import NetworkStreamSelectorForm
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -129,6 +130,13 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
text=device_stream_button_text, text=device_stream_button_text,
tooltip=device_stream_button_tooltip, tooltip=device_stream_button_tooltip,
triggers=self.on_open_device_stream) triggers=self.on_open_device_stream)
network_stream_button_text = translate('MediaPlugin.MediaItem', 'Open network stream')
network_stream_button_tooltip = translate('MediaPlugin.MediaItem', 'Open network stream')
self.open_network_stream = self.toolbar.add_toolbar_action('open_network_stream',
icon=UiIcons().network_stream,
text=network_stream_button_text,
tooltip=network_stream_button_tooltip,
triggers=self.on_open_network_stream)
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service, def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
**kwargs): **kwargs):
@ -165,9 +173,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
service_item.start_time = start service_item.start_time = start
service_item.end_time = end service_item.end_time = end
service_item.add_capability(ItemCapabilities.IsOptical) service_item.add_capability(ItemCapabilities.IsOptical)
elif filename.startswith('devicestream:'): elif filename.startswith('devicestream:') or filename.startswith('networkstream:'):
# Special handling if the filename is a devicestream # Special handling if the filename is a devicestream
(name, mrl, options) = parse_devicestream_path(filename) (name, mrl, options) = parse_stream_path(filename)
service_item.processor = 'vlc' service_item.processor = 'vlc'
service_item.add_from_command(filename, name, self.clapperboard) service_item.add_from_command(filename, name, self.clapperboard)
service_item.title = name service_item.title = name
@ -249,10 +257,13 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
item_name.setToolTip('{name}@{start}-{end}'.format(name=file_name, item_name.setToolTip('{name}@{start}-{end}'.format(name=file_name,
start=format_milliseconds(start), start=format_milliseconds(start),
end=format_milliseconds(end))) end=format_milliseconds(end)))
elif track_str.startswith('devicestream:'): elif track_str.startswith('devicestream:') or track_str.startswith('networkstream:'):
(name, mrl, options) = parse_devicestream_path(track_str) (name, mrl, options) = parse_stream_path(track_str)
item_name = QtWidgets.QListWidgetItem(name) item_name = QtWidgets.QListWidgetItem(name)
item_name.setIcon(UiIcons().device_stream) if track_str.startswith('devicestream:'):
item_name.setIcon(UiIcons().device_stream)
else:
item_name.setIcon(UiIcons().network_stream)
item_name.setData(QtCore.Qt.UserRole, track) item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip(mrl) item_name.setToolTip(mrl)
elif not os.path.exists(track): elif not os.path.exists(track):
@ -366,3 +377,32 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
file_paths.append(stream) file_paths.append(stream)
self.load_list([str(stream)]) self.load_list([str(stream)])
self.settings.setValue(self.settings_section + '/media files', file_paths) self.settings.setValue(self.settings_section + '/media files', file_paths)
def on_open_network_stream(self):
"""
When the open network stream button is clicked, open the stream selector window.
"""
if get_vlc():
stream_selector_form = NetworkStreamSelectorForm(self.main_window, self.add_network_stream)
stream_selector_form.exec()
del stream_selector_form
else:
critical_error_message_box(translate('MediaPlugin.MediaItem', 'VLC is not available'),
translate('MediaPlugin.MediaItem', 'Network streaming support requires VLC.'))
def add_network_stream(self, stream):
"""
Add a network stream based clip to the mediamanager, called from stream_selector_form.
:param stream: The clip to add.
"""
file_paths = self.get_file_list()
# If the clip already is in the media list it isn't added and an error message is displayed.
if stream in file_paths:
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Stream already saved'),
translate('MediaPlugin.MediaItem', 'This stream has already been saved'))
return
# Append the device stream string to the media list
file_paths.append(stream)
self.load_list([str(stream)])
self.settings.setValue(self.settings_section + '/media files', file_paths)