From ab54b044686b56fbdbc4bb75bfb93b3c2f7fbf00 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 25 Apr 2015 16:32:09 +0200 Subject: [PATCH] Fix VLC 2.2 on OS X which cannot be loaded due to an incorrect plugin path --- openlp/core/ui/media/vlcplayer.py | 93 ++++++++++++------- openlp/plugins/media/lib/mediaitem.py | 4 +- .../openlp_core_ui_media/test_vlcplayer.py | 53 +++++++++++ 3 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 tests/functional/openlp_core_ui_media/test_vlcplayer.py diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 535f23e8b..97f6a53dc 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -38,40 +38,6 @@ from openlp.core.ui.media.mediaplayer import MediaPlayer log = logging.getLogger(__name__) -VLC_AVAILABLE = False -try: - from openlp.core.ui.media.vendor import vlc - VLC_AVAILABLE = bool(vlc.get_default_instance()) -except (ImportError, NameError, NotImplementedError): - pass -except OSError as e: - if is_win(): - if not isinstance(e, WindowsError) and e.winerror != 126: - raise - elif is_macosx(): - pass - else: - raise - -if VLC_AVAILABLE: - try: - VERSION = vlc.libvlc_get_version().decode('UTF-8') - except: - VERSION = '0.0.0' - # LooseVersion does not work when a string contains letter and digits (e. g. 2.0.5 Twoflower). - # http://bugs.python.org/issue14894 - if LooseVersion(VERSION.split()[0]) < LooseVersion('1.1.0'): - VLC_AVAILABLE = False - log.debug('VLC could not be loaded, because the vlc version is too old: %s' % VERSION) - # On linux we need to initialise X threads, but not when running tests. - if VLC_AVAILABLE and is_linux() and 'nose' not in sys.argv[0]: - import ctypes - try: - x11 = ctypes.cdll.LoadLibrary('libX11.so') - x11.XInitThreads() - except: - log.exception('Failed to run XInitThreads(), VLC might not work properly!') - # Audio and video extensions copied from 'include/vlc_interface.h' from vlc 2.2.0 source AUDIO_EXT = ['*.3ga', '*.669', '*.a52', '*.aac', '*.ac3', '*.adt', '*.adts', '*.aif', '*.aifc', '*.aiff', '*.amr', '*.aob', '*.ape', '*.awb', '*.caf', '*.dts', '*.flac', '*.it', '*.kar', '*.m4a', '*.m4b', '*.m4p', '*.m5p', @@ -89,6 +55,57 @@ VIDEO_EXT = ['*.3g2', '*.3gp', '*.3gp2', '*.3gpp', '*.amv', '*.asf', '*.avi', '* '*.nut', '*.rv', '*.xvid'] +def get_vlc(): + """ + In order to make this module more testable, we have to wrap the VLC import inside a method. We do this so that we + can mock out the VLC module entirely. + + :return: The "vlc" module, or None + """ + is_vlc_available = False + try: + if is_macosx(): + # Newer versions of VLC on OS X need this. See https://forum.videolan.org/viewtopic.php?t=124521 + os.environ['VLC_PLUGIN_PATH'] = '/Applications/VLC.app/Contents/MacOS/plugins' + from openlp.core.ui.media.vendor import vlc + + is_vlc_available = bool(vlc.get_default_instance()) + except (ImportError, NameError, NotImplementedError): + pass + except OSError as e: + if is_win(): + if not isinstance(e, WindowsError) and e.winerror != 126: + raise + elif is_macosx(): + pass + else: + raise + + if is_vlc_available: + try: + VERSION = vlc.libvlc_get_version().decode('UTF-8') + except: + VERSION = '0.0.0' + # LooseVersion does not work when a string contains letter and digits (e. g. 2.0.5 Twoflower). + # http://bugs.python.org/issue14894 + if LooseVersion(VERSION.split()[0]) < LooseVersion('1.1.0'): + is_vlc_available = False + log.debug('VLC could not be loaded, because the vlc version is too old: %s' % VERSION) + # On linux we need to initialise X threads, but not when running tests. + if is_vlc_available and is_linux() and 'nose' not in sys.argv[0]: + import ctypes + try: + x11 = ctypes.cdll.LoadLibrary('libX11.so') + x11.XInitThreads() + except: + log.exception('Failed to run XInitThreads(), VLC might not work properly!') + + if is_vlc_available: + return vlc + else: + return None + + class VlcPlayer(MediaPlayer): """ A specialised version of the MediaPlayer class, which provides a VLC display. @@ -110,6 +127,7 @@ class VlcPlayer(MediaPlayer): """ Set up the media player """ + vlc = get_vlc() display.vlc_widget = QtGui.QFrame(display) display.vlc_widget.setFrameStyle(QtGui.QFrame.NoFrame) # creating a basic vlc instance @@ -145,12 +163,13 @@ class VlcPlayer(MediaPlayer): """ Return the availability of VLC """ - return VLC_AVAILABLE + return get_vlc() is not None def load(self, display): """ Load a video into VLC """ + vlc = get_vlc() log.debug('load vid in Vlc Controller') controller = display.controller volume = controller.media_info.volume @@ -190,6 +209,7 @@ class VlcPlayer(MediaPlayer): Wait for the video to change its state Wait no longer than 60 seconds. (loading an iso file needs a long time) """ + vlc = get_vlc() start = datetime.now() while not media_state == display.vlc_media.get_state(): if display.vlc_media.get_state() == vlc.State.Error: @@ -209,6 +229,7 @@ class VlcPlayer(MediaPlayer): """ Play the current item """ + vlc = get_vlc() controller = display.controller start_time = 0 log.debug('vlc play') @@ -254,6 +275,7 @@ class VlcPlayer(MediaPlayer): """ Pause the current item """ + vlc = get_vlc() if display.vlc_media.get_state() != vlc.State.Playing: return display.vlc_media_player.pause() @@ -303,6 +325,7 @@ class VlcPlayer(MediaPlayer): """ Update the UI """ + vlc = get_vlc() # Stop video if playback is finished. if display.vlc_media.get_state() == vlc.State.Ended: self.stop(display) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 4fd1620f3..f34e88926 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -33,8 +33,8 @@ from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adj from openlp.core.ui import DisplayController, Display, DisplayControllerType from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds from openlp.core.utils import get_locale_key -from openlp.core.ui.media.vlcplayer import VLC_AVAILABLE -if VLC_AVAILABLE: +from openlp.core.ui.media.vlcplayer import get_vlc +if get_vlc() is not None: from openlp.plugins.media.forms.mediaclipselectorform import MediaClipSelectorForm diff --git a/tests/functional/openlp_core_ui_media/test_vlcplayer.py b/tests/functional/openlp_core_ui_media/test_vlcplayer.py new file mode 100644 index 000000000..53e22a6e8 --- /dev/null +++ b/tests/functional/openlp_core_ui_media/test_vlcplayer.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2015 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; version 2 of the License. # +# # +# 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, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.ui.media.vlcplayer package. +""" +import os + +from unittest import TestCase +from tests.functional import patch + +from openlp.core.ui.media.vlcplayer import get_vlc + + +class TestVLCPlayer(TestCase): + """ + Test the functions in the :mod:`vlcplayer` module. + """ + + @patch.dict(os.environ) + @patch('openlp.core.ui.media.vlcplayer.is_macosx') + def fix_vlc_22_plugin_path_test(self, mocked_is_macosx): + """ + Test that on OS X we set the VLC plugin path to fix a bug in the VLC module + """ + # GIVEN: We're on OS X + mocked_is_macosx.return_value = True + + # WHEN: An checking if the player is available + get_vlc() + + # THEN: The extra environment variable should be there + self.assertIn('VLC_PLUGIN_PATH', os.environ, + 'The plugin path should be in the environment variables') + self.assertEqual('/Applications/VLC.app/Contents/MacOS/plugins', os.environ['VLC_PLUGIN_PATH'])