diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index f91c46a7d..494bd54c3 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -589,7 +589,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): else: controller.mediabar.actions['playbackPlay'].setVisible(False) controller.mediabar.actions['playbackPause'].setVisible(True) - controller.mediabar.actions['playbackStop'].setVisible(True) + controller.mediabar.actions['playbackStop'].setDisabled(False) if controller.is_live: if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background: controller.hide_menu.defaultAction().trigger() @@ -619,7 +619,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): display = self._define_display(controller) self.current_media_players[controller.controller_type].pause(display) controller.mediabar.actions['playbackPlay'].setVisible(True) - controller.mediabar.actions['playbackStop'].setVisible(True) + controller.mediabar.actions['playbackStop'].setDisabled(False) controller.mediabar.actions['playbackPause'].setVisible(False) def media_stop_msg(self, msg): @@ -645,7 +645,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): self.current_media_players[controller.controller_type].set_visible(display, False) controller.seek_slider.setSliderPosition(0) controller.mediabar.actions['playbackPlay'].setVisible(True) - controller.mediabar.actions['playbackStop'].setVisible(False) + controller.mediabar.actions['playbackStop'].setDisabled(True) controller.mediabar.actions['playbackPause'].setVisible(False) def media_volume_msg(self, msg): diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 46057fcf9..535f23e8b 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -27,10 +27,11 @@ from distutils.version import LooseVersion import logging import os import threading +import sys from PyQt4 import QtGui -from openlp.core.common import Settings, is_win, is_macosx +from openlp.core.common import Settings, is_win, is_macosx, is_linux from openlp.core.lib import translate from openlp.core.ui.media import MediaState, MediaType from openlp.core.ui.media.mediaplayer import MediaPlayer @@ -62,36 +63,30 @@ if VLC_AVAILABLE: 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_EXT = ['*.mp3', '*.wav', '*.wma', '*.ogg'] +# 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', + '*.mid', '*.mka', '*.mlp', '*.mod', '*.mpa', '*.mp1', '*.mp2', '*.mp3', '*.mpc', '*.mpga', '*.mus', + '*.oga', '*.ogg', '*.oma', '*.opus', '*.qcp', '*.ra', '*.rmi', '*.s3m', '*.sid', '*.spx', '*.thd', '*.tta', + '*.voc', '*.vqf', '*.w64', '*.wav', '*.wma', '*.wv', '*.xa', '*.xm'] -VIDEO_EXT = [ - '*.3gp', - '*.asf', '*.wmv', - '*.au', - '*.avi', - '*.divx', - '*.flv', - '*.mov', - '*.mp4', '*.m4v', - '*.ogm', '*.ogv', - '*.mkv', '*.mka', - '*.ts', '*.mpg', - '*.mpg', '*.mp2', - '*.nsc', - '*.nsv', - '*.nut', - '*.ra', '*.ram', '*.rm', '*.rv', '*.rmbv', - '*.a52', '*.dts', '*.aac', '*.flac', '*.dv', '*.vid', - '*.tta', '*.tac', - '*.ty', - '*.dts', - '*.xa', - '*.iso', - '*.vob', - '*.webm', - '*.xvid' -] +VIDEO_EXT = ['*.3g2', '*.3gp', '*.3gp2', '*.3gpp', '*.amv', '*.asf', '*.avi', '*.bik', '*.divx', '*.drc', '*.dv', + '*.f4v', '*.flv', '*.gvi', '*.gxf', '*.iso', '*.m1v', '*.m2v', '*.m2t', '*.m2ts', '*.m4v', '*.mkv', + '*.mov', '*.mp2', '*.mp2v', '*.mp4', '*.mp4v', '*.mpe', '*.mpeg', '*.mpeg1', '*.mpeg2', '*.mpeg4', '*.mpg', + '*.mpv2', '*.mts', '*.mtv', '*.mxf', '*.mxg', '*.nsv', '*.nuv', '*.ogg', '*.ogm', '*.ogv', '*.ogx', '*.ps', + '*.rec', '*.rm', '*.rmvb', '*.rpl', '*.thp', '*.tod', '*.ts', '*.tts', '*.txd', '*.vob', '*.vro', '*.webm', + '*.wm', '*.wmv', '*.wtv', '*.xesc', + # These extensions was not in the official list, added manually. + '*.nut', '*.rv', '*.xvid'] class VlcPlayer(MediaPlayer): diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index 338a1eae5..bff9cf940 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -375,7 +375,7 @@ class WebkitPlayer(MediaPlayer): # check if conversion was ok and value is not 'NaN' if length and length != float('inf'): length = int(length * 1000) - if current_time: + if current_time and length: controller.media_info.length = length controller.seek_slider.setMaximum(length) if not controller.seek_slider.isSliderDown(): diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 645940fe6..22fd0f4b3 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1069,8 +1069,13 @@ class SlideController(DisplayController, RegistryProperties): :param start: """ # Only one thread should be in here at the time. If already locked just skip, since the update will be - # done by the thread holding the lock. If it is a "start" slide, we must wait for the lock. - if not self.slide_selected_lock.acquire(start): + # done by the thread holding the lock. If it is a "start" slide, we must wait for the lock, but only for 0.2 + # seconds, since we don't want to cause a deadlock + timeout = 0.2 if start else -1 + if not self.slide_selected_lock.acquire(start, timeout): + if start: + self.log_debug('Could not get lock in slide_selected after waiting %f, skip to avoid deadlock.' + % timeout) return row = self.preview_widget.current_slide_number() self.selected_row = 0 diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 00a529b63..63fd20ba5 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -245,7 +245,8 @@ class PresentationMediaItem(MediaManagerItem): doc = self.controllers[cidx].add_document(filepath) if clean_for_update: thumb_path = doc.get_thumbnail_path(1, True) - if not thumb_path or os.path.getmtime(thumb_path) < os.path.getmtime(filepath): + if not thumb_path or not os.path.exists(filepath) or os.path.getmtime( + thumb_path) < os.path.getmtime(filepath): doc.presentation_deleted() else: doc.presentation_deleted() diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 8de65e0fe..72584e571 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -87,7 +87,7 @@ class TestMediaItem(TestCase, TestMixin): def clean_up_thumbnails_test(self): """ - Test that the clean_up_thumbnails method works as expected. + Test that the clean_up_thumbnails method works as expected when files exists. """ # GIVEN: A mocked controller, and mocked os.path.getmtime mocked_controller = MagicMock() @@ -98,8 +98,10 @@ class TestMediaItem(TestCase, TestMixin): 'Mocked': mocked_controller } presentation_file = 'file.tmp' - with patch('openlp.plugins.presentations.lib.mediaitem.os.path.getmtime') as mocked_getmtime: + with patch('openlp.plugins.presentations.lib.mediaitem.os.path.getmtime') as mocked_getmtime, \ + patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists: mocked_getmtime.side_effect = [100, 200] + mocked_exists.return_value = True # WHEN: calling clean_up_thumbnails self.media_item.clean_up_thumbnails(presentation_file, True) @@ -107,3 +109,25 @@ class TestMediaItem(TestCase, TestMixin): # THEN: doc.presentation_deleted should have been called since the thumbnails mtime will be greater than # the presentation_file's mtime. mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True) + + def clean_up_thumbnails_missing_file_test(self): + """ + Test that the clean_up_thumbnails method works as expected when file is missing. + """ + # GIVEN: A mocked controller, and mocked os.path.exists + mocked_controller = MagicMock() + mocked_doc = MagicMock() + mocked_controller.add_document.return_value = mocked_doc + mocked_controller.supports = ['tmp'] + self.media_item.controllers = { + 'Mocked': mocked_controller + } + presentation_file = 'file.tmp' + with patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists: + mocked_exists.return_value = False + + # WHEN: calling clean_up_thumbnails + self.media_item.clean_up_thumbnails(presentation_file, True) + + # THEN: doc.presentation_deleted should have been called since the presentation file did not exists. + mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True)