From 19a2deef3019195dcd40b54a525dc54be89a67a2 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 6 Nov 2015 21:43:31 +0000 Subject: [PATCH 01/12] allow local remote views --- openlp/plugins/remotes/lib/httprouter.py | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 000d743cd..644bb4841 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -150,6 +150,7 @@ class HttpRouter(RegistryProperties): self.routes = [ ('^/$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}), + ('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), @@ -170,6 +171,7 @@ class HttpRouter(RegistryProperties): self.settings_section = 'remotes' self.translate() self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') + self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages') def do_post_processor(self): """ @@ -340,6 +342,44 @@ class HttpRouter(RegistryProperties): 'settings': translate('RemotePlugin.Mobile', 'Settings'), } + def stages(self, temp_path, file_name): + """ + Allow Stage view to be delivered with custom views. + + :param temp_path: base path of the URL + :param file_name: file name with path + :return: + """ + log.debug('serve file request %s' % file_name) + parts = file_name.split('/') + if len(parts) == 3: + file_name = parts[0] + '/' + parts[2] + path = os.path.normpath(os.path.join(self.config_dir, file_name)) + print(path) + if not path.startswith(self.config_dir): + return self.do_not_found() + content = None + ext, content_type = self.get_content_type(path) + file_handle = None + try: + if ext == '.html': + variables = self.template_vars + content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) + else: + file_handle = open(path, 'rb') + log.debug('Opened %s' % path) + content = file_handle.read() + except IOError: + log.exception('Failed to open %s' % path) + return self.do_not_found() + finally: + if file_handle: + file_handle.close() + self.send_response(200) + self.send_header('Content-type', content_type) + self.end_headers() + return content + def serve_file(self, file_name=None): """ Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder. @@ -384,6 +424,8 @@ class HttpRouter(RegistryProperties): """ Examines the extension of the file and determines what the content_type should be, defaults to text/plain Returns the extension and the content_type + + :param file_name: name of file """ ext = os.path.splitext(file_name)[1] content_type = FILE_TYPES.get(ext, 'text/plain') @@ -392,6 +434,10 @@ class HttpRouter(RegistryProperties): def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None): """ Serve an image file. If not found return 404. + + :param file_name: file name to be served + :param dimensions: image size + :param controller_name: controller to be called """ log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name)) supported_controllers = ['presentations', 'images'] @@ -496,6 +542,8 @@ class HttpRouter(RegistryProperties): def controller_text(self, var): """ Perform an action on the slide controller. + + :param var: variable - not used """ log.debug("controller_text var = %s" % var) current_item = self.live_controller.service_item @@ -629,6 +677,8 @@ class HttpRouter(RegistryProperties): def go_live(self, plugin_name): """ Go live on an item of type ``plugin``. + + :param plugin_name: name of plugin """ try: request_id = json.loads(self.request_data)['request']['id'] @@ -642,6 +692,8 @@ class HttpRouter(RegistryProperties): def add_to_service(self, plugin_name): """ Add item of type ``plugin_name`` to the end of the service. + + :param plugin_name: name of plugin to be called """ try: request_id = json.loads(self.request_data)['request']['id'] From 7299aae51ce5fe336547b3ab38ae377b38705af0 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 8 Nov 2015 20:19:02 +0000 Subject: [PATCH 02/12] fix code --- openlp/plugins/remotes/lib/httprouter.py | 10 ++++++---- openlp/plugins/remotes/lib/httpserver.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 644bb4841..5f62b196a 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -150,7 +150,7 @@ class HttpRouter(RegistryProperties): self.routes = [ ('^/$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}), - ('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}), + ('^/(stages)/(.*)$', {'function': self.stages, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), @@ -352,10 +352,12 @@ class HttpRouter(RegistryProperties): """ log.debug('serve file request %s' % file_name) parts = file_name.split('/') - if len(parts) == 3: - file_name = parts[0] + '/' + parts[2] + if len(parts) == 1: + file_name = parts[0] + '/stage.html' + elif len(parts) == 3: + print(parts) + file_name = parts[1] + '/' + parts[2] path = os.path.normpath(os.path.join(self.config_dir, file_name)) - print(path) if not path.startswith(self.config_dir): return self.do_not_found() content = None diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index fa1348314..958088b00 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -167,7 +167,7 @@ class HTTPSServer(HTTPServer): local_data = AppLocation.get_directory(AppLocation.DataDir) self.socket = ssl.SSLSocket( sock=socket.socket(self.address_family, self.socket_type), - ssl_version=ssl.PROTOCOL_TLSv1, + ssl_version=ssl.PROTOCOL_TLSv1_2, certfile=os.path.join(local_data, 'remotes', 'openlp.crt'), keyfile=os.path.join(local_data, 'remotes', 'openlp.key'), server_side=True) From 1ff9413d5aeb01a17fc48f7e87761997adae8f4e Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 8 Nov 2015 20:23:16 +0000 Subject: [PATCH 03/12] remove s --- openlp/plugins/remotes/lib/httprouter.py | 27 +++++------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 5f62b196a..802c53226 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -150,7 +150,7 @@ class HttpRouter(RegistryProperties): self.routes = [ ('^/$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}), - ('^/(stages)/(.*)$', {'function': self.stages, 'secure': False}), + ('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), @@ -360,6 +360,9 @@ class HttpRouter(RegistryProperties): path = os.path.normpath(os.path.join(self.config_dir, file_name)) if not path.startswith(self.config_dir): return self.do_not_found() + return self._process_file(path) + + def _process_file(self, path): content = None ext, content_type = self.get_content_type(path) file_handle = None @@ -400,27 +403,7 @@ class HttpRouter(RegistryProperties): path = os.path.normpath(os.path.join(self.html_dir, file_name)) if not path.startswith(self.html_dir): return self.do_not_found() - content = None - ext, content_type = self.get_content_type(path) - file_handle = None - try: - if ext == '.html': - variables = self.template_vars - content = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) - else: - file_handle = open(path, 'rb') - log.debug('Opened %s' % path) - content = file_handle.read() - except IOError: - log.exception('Failed to open %s' % path) - return self.do_not_found() - finally: - if file_handle: - file_handle.close() - self.send_response(200) - self.send_header('Content-type', content_type) - self.end_headers() - return content + return self._process_file(path) def get_content_type(self, file_name): """ From 97e3eea17398133aec38eeceae07cd81c1ca06fd Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 8 Nov 2015 21:07:48 +0000 Subject: [PATCH 04/12] and add tests --- openlp/plugins/remotes/lib/httprouter.py | 6 ++++ .../openlp_plugins/remotes/test_router.py | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 802c53226..3bd207a28 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -363,6 +363,12 @@ class HttpRouter(RegistryProperties): return self._process_file(path) def _process_file(self, path): + """ + Common file processing code + + :param path: path to file to be loaded + :return: web resource to be loaded + """ content = None ext, content_type = self.get_content_type(path) file_handle = None diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index 70a2033a4..7f804cbc9 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -340,3 +340,39 @@ class TestRouter(TestCase, TestMixin): # THEN: service_manager.next_item() should have been called self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager') + + def remote_stage_personal_html_test(self): + """ + Test the stage url with a parameter after loaded a url/stage.html file + """ + # GIVEN: initial route + self.router.config_dir = '' + self.router.send_response = MagicMock() + self.router.send_header = MagicMock() + self.router.end_headers = MagicMock() + self.router.wfile = MagicMock() + self.router._process_file = MagicMock() + + # WHEN: I call stage with a suffix + self.router.stages('stages', 'trb') + + # THEN: we should use the specific stage file instance + self.router._process_file.assert_called_with('trb/stage.html') + + def remote_stage_personal_css_test(self): + """ + Test the html with reference stages/trb/trb.css then loaded a stages/trb/trb.css file + """ + # GIVEN: initial route + self.router.config_dir = '' + self.router.send_response = MagicMock() + self.router.send_header = MagicMock() + self.router.end_headers = MagicMock() + self.router.wfile = MagicMock() + self.router._process_file = MagicMock() + + # WHEN: I call stage with a suffix + self.router.stages('stages', 'stages/trb/trb.css') + + # THEN: we should use the specific stage file instance + self.router._process_file.assert_called_with('trb/trb.css') \ No newline at end of file From ae21d548ba0d094b2e1c76a360bf6b72ad49a1d9 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 8 Nov 2015 21:19:54 +0000 Subject: [PATCH 05/12] fix join --- openlp/plugins/remotes/lib/httprouter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 3bd207a28..78afd4de5 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -353,10 +353,10 @@ class HttpRouter(RegistryProperties): log.debug('serve file request %s' % file_name) parts = file_name.split('/') if len(parts) == 1: - file_name = parts[0] + '/stage.html' + file_name = os.path.join(parts[0], 'stage.html') elif len(parts) == 3: print(parts) - file_name = parts[1] + '/' + parts[2] + file_name = os.path.join(parts[1], parts[2]) path = os.path.normpath(os.path.join(self.config_dir, file_name)) if not path.startswith(self.config_dir): return self.do_not_found() From b4d7798300e3ac6dd80f8ca47e99a9518f97ed58 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sun, 8 Nov 2015 21:23:15 +0000 Subject: [PATCH 06/12] fix join in tests --- tests/functional/openlp_plugins/remotes/test_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index 7f804cbc9..25f55972c 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -357,7 +357,7 @@ class TestRouter(TestCase, TestMixin): self.router.stages('stages', 'trb') # THEN: we should use the specific stage file instance - self.router._process_file.assert_called_with('trb/stage.html') + self.router._process_file.assert_called_with(os.path.join('trb', 'stage.html')) def remote_stage_personal_css_test(self): """ @@ -375,4 +375,4 @@ class TestRouter(TestCase, TestMixin): self.router.stages('stages', 'stages/trb/trb.css') # THEN: we should use the specific stage file instance - self.router._process_file.assert_called_with('trb/trb.css') \ No newline at end of file + self.router._process_file.assert_called_with(os.path.join('trb', 'trb.css')) From ab18eaa051744233d81abf39e633599ebbc24790 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 5 Dec 2015 12:44:37 +0000 Subject: [PATCH 07/12] fixes --- openlp/core/ui/maindisplay.py | 3 +++ openlp/plugins/remotes/lib/httprouter.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index d59090d40..1a18b8d96 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -293,12 +293,15 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): if self.height() != self.screen['size'].height() or not self.isVisible(): shrink = True js = 'show_alert("%s", "%s")' % (text_prepared, 'top') + print(js) else: shrink = False js = 'show_alert("%s", "")' % text_prepared + print(js) height = self.frame.evaluateJavaScript(js) if shrink: if text: + print(height) alert_height = int(height) self.resize(self.width(), alert_height) self.setVisible(True) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 78afd4de5..d2acc4ddf 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -342,11 +342,11 @@ class HttpRouter(RegistryProperties): 'settings': translate('RemotePlugin.Mobile', 'Settings'), } - def stages(self, temp_path, file_name): + def stages(self, url_path, file_name): """ Allow Stage view to be delivered with custom views. - :param temp_path: base path of the URL + :param url_path: base path of the URL. Not used but passed by caller :param file_name: file name with path :return: """ @@ -355,7 +355,6 @@ class HttpRouter(RegistryProperties): if len(parts) == 1: file_name = os.path.join(parts[0], 'stage.html') elif len(parts) == 3: - print(parts) file_name = os.path.join(parts[1], parts[2]) path = os.path.normpath(os.path.join(self.config_dir, file_name)) if not path.startswith(self.config_dir): From 9a950ea4ab70da9a62e772830cbdad624d018a2d Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 5 Dec 2015 12:45:51 +0000 Subject: [PATCH 08/12] remove prints --- openlp/core/ui/maindisplay.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 1a18b8d96..d59090d40 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -293,15 +293,12 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): if self.height() != self.screen['size'].height() or not self.isVisible(): shrink = True js = 'show_alert("%s", "%s")' % (text_prepared, 'top') - print(js) else: shrink = False js = 'show_alert("%s", "")' % text_prepared - print(js) height = self.frame.evaluateJavaScript(js) if shrink: if text: - print(height) alert_height = int(height) self.resize(self.width(), alert_height) self.setVisible(True) From f241aa663de7b85c3f7418ded060defd44f760c9 Mon Sep 17 00:00:00 2001 From: Jonathan Springer Date: Mon, 7 Dec 2015 16:31:46 -0500 Subject: [PATCH 09/12] Add test to reproduce traceback in mediacontroller --- .../test_mediacontroller.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/functional/openlp_core_ui_media/test_mediacontroller.py b/tests/functional/openlp_core_ui_media/test_mediacontroller.py index d78f9a06b..25132f05b 100644 --- a/tests/functional/openlp_core_ui_media/test_mediacontroller.py +++ b/tests/functional/openlp_core_ui_media/test_mediacontroller.py @@ -120,6 +120,36 @@ class TestMediaController(TestCase, TestMixin): # THEN: it should return False self.assertFalse(ret, '_check_file_type should return False when the processor for service_item is None.') + @patch('openlp.core.ui.media.mediacontroller.get_media_players') + @patch('openlp.core.ui.media.mediacontroller.UiStrings') + def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_media_players): + """ + Test that we can play media when players are available and we have a automatic processor from the service item + """ + # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item + mocked_get_media_players.return_value = (['vlc', 'webkit'], '') + mocked_ret_uistrings = MagicMock() + mocked_ret_uistrings.Automatic = 1 + mocked_uistrings.return_value = mocked_ret_uistrings + media_controller = MediaController() + mocked_vlc = MagicMock() + mocked_vlc.video_extensions_list = ['*.mp4'] + media_controller.media_players = {'vlc': mocked_vlc, 'webkit': MagicMock()} + mocked_controller = MagicMock() + mocked_suffix = MagicMock() + mocked_suffix.return_value = 'mp4' + mocked_controller.media_info.file_info.suffix = mocked_suffix + mocked_display = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.processor = 1 + + # WHEN: calling _check_file_type when the processor for the service item is None + ret = media_controller._check_file_type(mocked_controller, mocked_display, mocked_service_item) + + # THEN: it should return False + self.assertTrue(ret, '_check_file_type should return True when mediaplayers are available and ' + 'the service item has an automatic processor.') + def media_play_msg_test(self): """ Test that the media controller responds to the request to play a loaded video From ae4858e4347109e68bad91d088ade64fa1ecb241 Mon Sep 17 00:00:00 2001 From: Jonathan Springer Date: Mon, 7 Dec 2015 16:34:10 -0500 Subject: [PATCH 10/12] Fix traceback in mediacontroller if a service item had a processor type of automatic --- openlp/core/ui/media/mediacontroller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 6d35f2478..2679e836e 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -514,7 +514,10 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): :param display: Which display to use :param service_item: The ServiceItem containing the details to be played. """ - used_players = get_media_players() + used_players = get_media_players()[0] + # If no player, we can't play + if not used_players: + return False default_player = used_players[0] if service_item.processor and service_item.processor != UiStrings().Automatic: # check to see if the player is usable else use the default one. @@ -522,9 +525,6 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): used_players = default_player else: used_players = [service_item.processor.lower()] - # If no player, we can't play - if not used_players: - return False if controller.media_info.file_info.isFile(): suffix = '*.%s' % controller.media_info.file_info.suffix().lower() for title in used_players: From 3108adff17c2a0213830a997d0d1468cbd852331 Mon Sep 17 00:00:00 2001 From: Jonathan Springer Date: Thu, 10 Dec 2015 12:25:34 -0500 Subject: [PATCH 11/12] Add test for failing situation --- .../test_mediacontroller.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/functional/openlp_core_ui_media/test_mediacontroller.py b/tests/functional/openlp_core_ui_media/test_mediacontroller.py index 25132f05b..c207d1d99 100644 --- a/tests/functional/openlp_core_ui_media/test_mediacontroller.py +++ b/tests/functional/openlp_core_ui_media/test_mediacontroller.py @@ -146,10 +146,40 @@ class TestMediaController(TestCase, TestMixin): # WHEN: calling _check_file_type when the processor for the service item is None ret = media_controller._check_file_type(mocked_controller, mocked_display, mocked_service_item) - # THEN: it should return False + # THEN: it should return True self.assertTrue(ret, '_check_file_type should return True when mediaplayers are available and ' 'the service item has an automatic processor.') + @patch('openlp.core.ui.media.mediacontroller.get_media_players') + @patch('openlp.core.ui.media.mediacontroller.UiStrings') + def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_media_players): + """ + Test that we can play media when players available are different from the processor from the service item + """ + # GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item + mocked_get_media_players.return_value = (['phonon'], '') + mocked_ret_uistrings = MagicMock() + mocked_ret_uistrings.Automatic = 'automatic' + mocked_uistrings.return_value = mocked_ret_uistrings + media_controller = MediaController() + mocked_phonon = MagicMock() + mocked_phonon.video_extensions_list = ['*.mp4'] + media_controller.media_players = {'phonon': mocked_phonon} + mocked_controller = MagicMock() + mocked_suffix = MagicMock() + mocked_suffix.return_value = 'mp4' + mocked_controller.media_info.file_info.suffix = mocked_suffix + mocked_display = MagicMock() + mocked_service_item = MagicMock() + mocked_service_item.processor = 'vlc' + + # WHEN: calling _check_file_type when the processor for the service item is None + ret = media_controller._check_file_type(mocked_controller, mocked_display, mocked_service_item) + + # THEN: it should return True + self.assertTrue(ret, '_check_file_type should return True when the players available are different' + 'from the processor from the service item.') + def media_play_msg_test(self): """ Test that the media controller responds to the request to play a loaded video From fc3c433a21251081e1e13cdfcf02dc3edbfba127 Mon Sep 17 00:00:00 2001 From: Jonathan Springer Date: Thu, 10 Dec 2015 12:26:08 -0500 Subject: [PATCH 12/12] Fix failing situation by making default_players a list instead of a string --- openlp/core/ui/media/mediacontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 2679e836e..1440c187f 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -518,7 +518,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): # If no player, we can't play if not used_players: return False - default_player = used_players[0] + default_player = [used_players[0]] if service_item.processor and service_item.processor != UiStrings().Automatic: # check to see if the player is usable else use the default one. if not service_item.processor.lower() in used_players: