Merge branch 'vlc_5' into 'master'

VLC - Cleanup

See merge request openlp/openlp!613
This commit is contained in:
Raoul Snyman 2023-06-01 04:40:00 +00:00
commit 06da7904ba
8 changed files with 1037 additions and 256 deletions

View File

@ -86,12 +86,12 @@ class ItemMediaInfo(object):
"""
file_info = None
is_background = False
is_theme_background = None
length = 0
start_time = 0
end_time = 0
title_track = 0
is_playing = False
old_timer = -1
timer = 1000
audio_track = 0
subtitle_track = 0

View File

@ -32,7 +32,7 @@ except ImportError:
pymediainfo_available = False
pymediainfo_version = '0.0'
from PyQt5 import QtCore
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin, RegistryProperties
@ -49,18 +49,24 @@ from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_opt
get_volume, toggle_looping_playback, is_looping_playback, save_volume
from openlp.core.ui.media.remote import register_views
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
from openlp.core.ui.media.vlcplayerpl import VlcPlayerPL
log = logging.getLogger(__name__)
TICK_TIME = 200
HIDE_DELAY_TIME = 2500
class MediaController(RegistryBase, LogMixin, RegistryProperties):
class MediaController(QtWidgets.QWidget, RegistryBase, LogMixin, RegistryProperties):
"""
The implementation of the Media Controller which manages how media is played.
"""
vlc_live_media_tick = QtCore.pyqtSignal()
vlc_preview_media_tick = QtCore.pyqtSignal()
vlc_live_media_stop = QtCore.pyqtSignal()
vlc_preview_media_stop = QtCore.pyqtSignal()
def __init__(self, parent=None):
"""
"""
@ -68,23 +74,17 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
self.log_info('MediaController Initialising')
def setup(self):
self.is_theme_background = False
self.vlc_player = None
self.vlc_playerpl = None
self.current_media_players = {}
# Timer for video state
self.live_timer = QtCore.QTimer()
self.live_timer.setInterval(TICK_TIME)
self.live_hide_timer = QtCore.QTimer()
self.live_hide_timer.setSingleShot(True)
self.live_kill_timer = QtCore.QTimer()
self.live_kill_timer.setSingleShot(True)
self.preview_timer = QtCore.QTimer()
self.preview_timer.setInterval(TICK_TIME)
# Signals
self.live_timer.timeout.connect(self._media_state_live)
self.live_hide_timer.timeout.connect(self._on_media_hide_live)
self.live_kill_timer.timeout.connect(self._on_media_kill_live)
self.preview_timer.timeout.connect(self._media_state_preview)
Registry().register_function('playbackPlay', self.media_play_msg)
Registry().register_function('playbackPause', self.media_pause_msg)
Registry().register_function('playbackStop', self.media_stop_msg)
@ -106,6 +106,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
self.setup()
self.vlc_player = VlcPlayer(self)
self.vlc_playerpl = VlcPlayerPL(self)
State().add_service('mediacontroller', 0)
State().add_service('media_live', 0)
has_vlc = get_vlc()
@ -145,6 +146,10 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
if State().check_preconditions('mediacontroller'):
try:
self.vlc_live_media_tick.connect(self._media_state_live)
self.vlc_preview_media_tick.connect(self._media_state_preview)
self.vlc_live_media_stop.connect(self.live_media_stopped)
self.vlc_preview_media_stop.connect(self.preview_media_stopped)
self.setup_display(self.live_controller, False)
except AttributeError:
State().update_pre_conditions('media_live', False)
@ -152,7 +157,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
'OpenLP.MediaController', 'No Displays have been configured, so Live Media has been disabled'))
self.setup_display(self.preview_controller, True)
def _display_controllers(self, controller_type: DisplayControllerType) -> Type[SlideController]:
def _display_controllers(self, controller_type: DisplayControllerType) -> SlideController:
"""
Decides which controller to use.
@ -165,16 +170,14 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
def _media_state_live(self) -> None:
"""
Check if there is a running Live media Player and do updating stuff (e.g. update the UI)
Check if there is a running Live media Player and do some updating stuff (e.g. update the UI)
"""
if DisplayControllerType.Live in self.current_media_players:
media_player = self.current_media_players[DisplayControllerType.Live]
media_player.resize(self.live_controller)
media_player.update_ui(self.live_controller, self._define_display(self.live_controller))
if not self.tick(self.live_controller):
self.live_timer.stop()
self.tick(self.live_controller)
else:
self.live_timer.stop()
self.media_stop(self.live_controller)
def _media_state_preview(self) -> None:
@ -185,13 +188,26 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
media_player = self.current_media_players[DisplayControllerType.Preview]
media_player.resize(self.preview_controller)
media_player.update_ui(self.preview_controller, self._define_display(self.preview_controller))
if not self.tick(self.preview_controller):
self.preview_timer.stop()
self.tick(self.preview_controller)
else:
self.preview_timer.stop()
self.media_stop(self.preview_controller)
def setup_display(self, controller: Type[SlideController], preview: bool) -> None:
def live_media_stopped(self) -> None:
self.media_stop(self.live_controller)
self.tick(self.live_controller)
if Registry().get('settings').value('media/live loop') or self.live_controller.media_info.is_theme_background:
self.has_started = False
self.media_play(self.live_controller)
def preview_media_stopped(self) -> None:
self.media_stop(self.preview_controller)
self.tick(self.preview_controller)
if Registry().get('settings').value('media/preview loop') or \
self.preview_controller.media_info.is_theme_background:
self.has_started = False
self.media_play(self.preview_controller)
def setup_display(self, controller: SlideController, preview: bool) -> None:
"""
After a new display is configured, all media related widgets will be created too
@ -203,9 +219,10 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
if preview:
controller.has_audio = False
self.vlc_player.setup(controller, self._define_display(controller))
self.vlc_playerpl.setup(controller, self._define_display(controller))
@staticmethod
def set_controls_visible(controller: Type[SlideController], value: int) -> None:
def set_controls_visible(controller: SlideController, value: int) -> None:
"""
After a new display is configured, all media related widget will be created too
@ -216,7 +233,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.mediabar.setVisible(value)
@staticmethod
def resize(controller: Type[SlideController], player) -> None:
def _resize(controller: SlideController, player) -> None:
"""
After Mainwindow changes or Splitter moved all related media widgets have to be resized
@ -225,22 +242,23 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
player.resize(controller)
def load_video(self, source, service_item, hidden: bool = False, is_theme_background: bool = False) -> None:
def load_video(self, source, service_item, hidden: bool = False, is_theme_background: bool = False) -> bool:
"""
Loads and starts a video to run and sets the stored sound value.
:param source: Where the call originated form
:param service_item: The player which is doing the playing
:param hidden: The player which is doing the playing
:param is_theme_background: Is the theme providing a background
"""
self.is_theme_background = is_theme_background
is_valid = True
controller = self._display_controllers(source)
controller.media_info.is_theme_background = is_theme_background
log.debug(f'load_video is_live:{controller.is_live}')
# stop running videos
self.media_reset(controller)
controller.media_info = ItemMediaInfo()
controller.media_info.is_theme_background = is_theme_background
controller.media_info.media_type = MediaType.Video
# background will always loop video.
if service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
@ -282,7 +300,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.timer = service_item.start_time
controller.media_info.end_time = service_item.end_time
is_valid = self._check_file_type(controller, display)
if not is_valid:
# Media could not be loaded correctly
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
@ -307,8 +324,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
'Unable to preview when live is currently streaming'))
return
self._media_bar(controller, 'load')
if self.decide_autoplay(service_item, controller, hidden):
start_hidden = self.is_theme_background and controller.is_live and \
start_hidden = controller.media_info.is_theme_background and controller.is_live and \
(controller.current_hide_mode == HideMode.Blank or controller.current_hide_mode == HideMode.Screen)
if not self.media_play(controller, start_hidden):
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
@ -341,7 +359,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# Unblank on load set
elif self.settings.value('core/auto unblank'):
is_autoplay = True
if self.is_theme_background:
if controller.media_info.is_theme_background:
is_autoplay = True
if controller.media_info.media_type == MediaType.Stream:
is_autoplay = True
return is_autoplay
@ -367,7 +387,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
return 0
def media_setup_optical(self, filename, title, audio_track, subtitle_track, start, end,
display: Type[DisplayWindow], controller: Type[SlideController]):
display: Type[DisplayWindow], controller: SlideController):
"""
Setup playback of optical media
@ -401,11 +421,11 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
if display is None:
display = controller.preview_display
self.vlc_player.load(controller, display, filename)
self.resize(controller, self.vlc_player)
self._resize(controller, self.vlc_player)
self.current_media_players[controller.controller_type] = self.vlc_player
return True
def _check_file_type(self, controller: Type[SlideController], display: Type[DisplayWindow]):
def _check_file_type(self, controller: SlideController, display: DisplayWindow):
"""
Select the correct media Player type from the prioritized Player list
@ -413,18 +433,18 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param display: Which display to use
"""
if controller.media_info.media_type == MediaType.Stream:
self.resize(controller, self.vlc_player)
self._resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, controller.media_info.file_info):
self.current_media_players[controller.controller_type] = self.vlc_player
return True
return False
for file in controller.media_info.file_info:
if not file.is_file and not self.vlc_player.can_folder:
if not file.is_file and not self.vlc_playerpl.can_folder:
return False
file = str(file)
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
self._resize(controller, self.vlc_playerpl)
if self.vlc_playerpl.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_playerpl
return True
return False
@ -443,7 +463,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
return self.media_play(self.live_controller)
def media_play(self, controller: Type[SlideController], start_hidden=False):
def media_play(self, controller: SlideController, start_hidden=False):
"""
Responds to the request to play a loaded video
@ -463,12 +483,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
self._media_set_visibility(controller, True)
self._media_bar(controller, "play")
# Start Timer for ui updates
if controller.is_live:
if not self.live_timer.isActive():
self.live_timer.start()
else:
if not self.preview_timer.isActive():
self.preview_timer.start()
controller.mediabar.seek_slider.blockSignals(False)
controller.mediabar.volume_slider.blockSignals(False)
controller.media_info.is_playing = True
@ -482,43 +496,38 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.output_has_changed()
return True
def tick(self, controller):
def tick(self, controller) -> None:
"""
Add a tick while the media is playing but only count if not paused
:param controller: The Controller to be processed
:return: Is the video still running?
"""
start_again = False
stopped = False
if controller.media_info.is_playing and controller.media_info.length > 0:
controller.media_info.timer = controller.vlc_media_player.get_time()
if controller.media_info.old_timer > controller.media_info.timer:
if is_looping_playback(controller) or self.is_theme_background:
start_again = True
else:
self.media_stop(controller)
stopped = True
controller.media_info.old_timer = controller.media_info.timer
self._update_seek_ui(controller)
else:
stopped = True
controller.media_info.timer = controller.vlc_media_player.get_time()
self._update_seek_ui(controller)
return
if start_again:
controller.media_info.timer = controller.media_info.start_time
self._update_seek_ui(controller)
return not stopped
def _media_bar(self, controller: Type[SlideController], mode: str) -> None:
def _media_bar(self, controller: SlideController, mode: str) -> None:
"""
Set the media bar state depending on the function called.
:param controller: The controller being updated
:param mode: The mode the code is being called from
:return: None
"""
loop_set = self.is_theme_background or is_looping_playback(controller)
loop_disabled = controller.media_info.media_type is MediaType.Stream or self.is_theme_background
controller.mediabar.blockSignals(True)
if controller.controller_type in self.current_media_players and \
self.current_media_players[controller.controller_type].can_repeat:
if controller.media_info.is_theme_background:
loop_set = False
loop_disabled = True
self.current_media_players[controller.controller_type].toggle_loop(controller, True)
else:
loop_set = is_looping_playback(controller)
loop_disabled = False
self.current_media_players[controller.controller_type].toggle_loop(controller, loop_set)
else:
loop_set = False
loop_disabled = True
if mode == "load":
controller.mediabar.actions['playbackPlay'].setDisabled(False)
controller.mediabar.actions['playbackPause'].setDisabled(True)
@ -544,7 +553,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
def _update_seek_ui(controller):
if controller.media_info.timer > controller.media_info.end_time:
controller.media_info.timer = controller.media_info.end_time
if controller.media_info.timer < 0:
controller.media_info.timer = 0
seconds = controller.media_info.timer // 1000
minutes = seconds // 60
seconds %= 60
@ -571,7 +581,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
return self.media_pause(self.live_controller)
def media_pause(self, controller: Type[SlideController]):
def media_pause(self, controller: SlideController):
"""
Responds to the request to pause a loaded video
@ -597,14 +607,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
self.media_loop(msg[0])
@staticmethod
def media_loop(controller: Type[SlideController]):
def media_loop(self, controller: Type[SlideController]):
"""
Responds to the request to loop a loaded video
:param controller: The controller that needs to be stopped
"""
toggle_looping_playback(controller)
if controller.controller_type in self.current_media_players:
self.current_media_players[controller.controller_type].toggle_loop(controller,
is_looping_playback(controller))
controller.mediabar.actions['playbackLoop'].setChecked(is_looping_playback(controller))
def media_stop_msg(self, msg: list):
@ -621,7 +633,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
return self.media_stop(self.live_controller)
def media_stop(self, controller: Type[SlideController]):
def media_stop(self, controller: SlideController):
"""
Responds to the request to stop a loaded video
@ -682,7 +694,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
seek_value = msg[1][0]
self.media_seek(controller, seek_value)
def media_seek(self, controller: Type[SlideController], seek_value):
def media_seek(self, controller: SlideController, seek_value):
"""
Responds to the request to change the seek Slider of a loaded video
@ -695,7 +707,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.timer = seek_value
self._update_seek_ui(controller)
def media_reset(self, controller: Type[SlideController], delayed: bool = False) -> None:
def media_reset(self, controller: SlideController, delayed: bool = False) -> None:
"""
Responds to the request to reset a loaded video
:param controller: The controller to use.
@ -713,6 +725,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
else:
self._media_set_visibility(controller, False)
del self.current_media_players[controller.controller_type]
controller.media_info = ItemMediaInfo()
controller.media_info.is_theme_background = False
self._media_bar(controller, 'reset')
def media_hide_msg(self, msg: list):
"""
@ -749,7 +764,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
self._media_set_visibility(self.live_controller, False)
del self.current_media_players[self.live_controller.controller_type]
def _media_set_visibility(self, controller: Type[SlideController], visible):
def _media_set_visibility(self, controller: SlideController, visible):
"""
Set the live video Widget visibility
"""
@ -782,13 +797,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
Registry().execute('live_display_hide', hide_mode)
controller_type = self.live_controller.controller_type
playing = self.current_media_players[controller_type].get_live_state() == MediaState.Playing
if self.is_theme_background and hide_mode == HideMode.Theme:
if self.live_controller.media_info.is_theme_background and hide_mode == HideMode.Theme:
if not playing:
self.media_play(self.live_controller)
else:
self.live_hide_timer.stop()
else:
if playing and not self.is_theme_background:
if playing and not self.live_controller.media_info.is_theme_background:
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
@ -818,15 +833,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
Reset all the media controllers when OpenLP shuts down
"""
self.live_timer.stop()
self.live_hide_timer.stop()
self.live_kill_timer.stop()
self.preview_timer.stop()
self.media_reset(self._display_controllers(DisplayControllerType.Live))
self.media_reset(self._display_controllers(DisplayControllerType.Preview))
@staticmethod
def _define_display(controller: SlideController) -> Type[DisplayWindow]:
def _define_display(controller: SlideController) -> DisplayWindow:
"""
Extract the correct display for a given controller

View File

@ -22,7 +22,11 @@
The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class.
"""
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.platform import is_macosx, is_win
from openlp.core.display.screens import ScreenList
from openlp.core.display.window import DisplayWindow
from openlp.core.ui.media import MediaState
from openlp.core.ui.slidecontroller import SlideController
class MediaPlayer(RegistryProperties):
@ -41,6 +45,7 @@ class MediaPlayer(RegistryProperties):
self.can_folder = False
self.state = {0: MediaState.Off, 1: MediaState.Off}
self.has_own_widget = False
self.can_repeat = False
def check_available(self):
"""
@ -66,13 +71,35 @@ class MediaPlayer(RegistryProperties):
"""
return True
def resize(self, controller):
def add_display(self, controller: SlideController):
# The media player has to be 'connected' to the QFrame.
# (otherwise a video would be displayed in it's own window)
# This is platform specific!
# You have to give the id of the QFrame (or similar object)
# to vlc, different platforms have different functions for this.
win_id = int(controller.vlc_widget.winId())
if is_win():
controller.vlc_media_player.set_hwnd(win_id)
elif is_macosx():
# We have to use 'set_nsobject' since Qt5 on OSX uses Cocoa
# framework and not the old Carbon.
controller.vlc_media_player.set_nsobject(win_id)
else:
# for Linux/*BSD using the X Server
controller.vlc_media_player.set_xwindow(win_id)
self.has_own_widget = True
def resize(self, controller: SlideController) -> None:
"""
If the main display size or position is changed, the media widgets
should also resized
:param controller: Which Controller is running the show.
Resize the player
:param controller: The display where the media is stored within the controller.
:return:
"""
pass
if controller.is_live:
controller.vlc_widget.setGeometry(ScreenList().current.display_geometry)
else:
controller.vlc_widget.resize(controller.preview_display.size())
def play(self, controller, display):
"""
@ -99,68 +126,65 @@ class MediaPlayer(RegistryProperties):
"""
pass
def volume(self, controller, volume):
def volume(self, controller: SlideController, vol: int) -> None:
"""
Change volume of current Media File
Set the volume
:param controller: Which Controller is running the show.
:param volume: The volume to set.
:param vol: The volume to be sets
:param controller: The controller where the media is
:return:
"""
pass
controller.vlc_media_player.audio_set_volume(vol)
def seek(self, controller, seek_value):
def seek(self, controller: SlideController, seek_value: int) -> None:
"""
Change playing position of current Media File
Go to a particular position
:param controller: Which Controller is running the show.
:param seek_value: The where to seek to.
:param seek_value: The position of where a seek goes to
:param controller: The controller where the media is
"""
pass
if controller.vlc_media_player.is_seekable():
controller.vlc_media_player.set_time(seek_value)
def reset(self, controller):
def reset(self, controller: SlideController) -> None:
"""
Remove the current loaded video
Reset the player
:param controller: Which Controller is running the show.
:param controller: The controller where the media is
"""
pass
controller.vlc_media_player.stop()
self.set_state(MediaState.Off, controller)
def set_visible(self, controller, status):
def set_visible(self, controller: SlideController, status: bool) -> None:
"""
Show/Hide the media widgets
Set the visibility
:param controller: Which Controller is running the show.
:param status: The status to be set.
:param controller: The controller where the media display is
:param status: The visibility status
"""
pass
controller.vlc_widget.setVisible(status)
def update_ui(self, controller, output_display):
def update_ui(self, controller: SlideController, output_display: DisplayWindow) -> None:
"""
Do some ui related stuff (e.g. update the seek slider)
Update the UI
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
"""
if not controller.mediabar.seek_slider.isSliderDown():
controller.mediabar.seek_slider.blockSignals(True)
controller.mediabar.seek_slider.setSliderPosition(controller.vlc_media_player.get_time())
controller.mediabar.seek_slider.blockSignals(False)
def toggle_loop(self, controller, loop_required: bool) -> None:
"""
Changes the looping style
:param controller: Which Controller is running the show.
:param loop_required: Are we to be toggled or not
:return: none
"""
pass
def get_media_display_css(self):
"""
Add css style sheets to htmlbuilder
"""
return ''
def get_media_display_javascript(self):
"""
Add javascript functions to htmlbuilder
"""
return ''
def get_media_display_html(self):
"""
Add html code to htmlbuilder
"""
return ''
def get_live_state(self):
"""
Get the state of the live player

View File

@ -28,14 +28,12 @@ import sys
import threading
from datetime import datetime
from time import sleep
from typing import Type
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin
from openlp.core.common.platform import is_linux, is_macosx, is_win
from openlp.core.display.screens import ScreenList
from openlp.core.common.platform import is_linux, is_win
from openlp.core.display.window import DisplayWindow
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.slidecontroller import SlideController
@ -102,7 +100,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
self.parent = parent
self.can_folder = True
def setup(self, controller: SlideController, display: Type[DisplayWindow]) -> None:
def setup(self, controller: SlideController, display: DisplayWindow) -> None:
"""
Set up the media player
@ -142,22 +140,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
controller.vlc_media_player = controller.vlc_instance.media_player_new()
controller.vlc_widget.resize(controller.size())
controller.vlc_widget.hide()
# The media player has to be 'connected' to the QFrame.
# (otherwise a video would be displayed in it's own window)
# This is platform specific!
# You have to give the id of the QFrame (or similar object)
# to vlc, different platforms have different functions for this.
win_id = int(controller.vlc_widget.winId())
if is_win():
controller.vlc_media_player.set_hwnd(win_id)
elif is_macosx():
# We have to use 'set_nsobject' since Qt5 on OSX uses Cocoa
# framework and not the old Carbon.
controller.vlc_media_player.set_nsobject(win_id)
else:
# for Linux/*BSD using the X Server
controller.vlc_media_player.set_xwindow(win_id)
self.has_own_widget = True
self.add_display(controller)
def check_available(self):
"""
@ -165,9 +148,9 @@ class VlcPlayer(MediaPlayer, LogMixin):
"""
return get_vlc() is not None
def load(self, controller: SlideController, output_display: Type[DisplayWindow], file: str) -> bool:
def load(self, controller: SlideController, output_display: DisplayWindow, file: str) -> bool:
"""
Load a video into VLC
Load a Stream or DVD into VLC
:param controller: The controller where the media is
:param output_display: The display where the media is
@ -177,6 +160,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
if not controller.vlc_instance:
return False
self.log_debug('load video in VLC Controller')
self.add_display(controller)
path = None
if file and not controller.media_info.media_type == MediaType.Stream:
path = os.path.normcase(file)
@ -219,15 +203,11 @@ class VlcPlayer(MediaPlayer, LogMixin):
res = controller.vlc_media_player.video_set_spu(controller.media_info.subtitle_track)
self.log_debug('vlc play, subtitle_track set: ' +
str(controller.media_info.subtitle_track) + ' ' + str(res))
elif controller.media_info.media_type == MediaType.Stream:
else:
# We must be Streaming
controller.vlc_media = controller.vlc_instance.media_new_location(file[0])
controller.vlc_media.add_options(file[1])
controller.vlc_media_player.set_media(controller.vlc_media)
else:
controller.vlc_media = controller.vlc_instance.media_new_path(path)
controller.vlc_media_player.set_media(controller.vlc_media)
controller.media_info.start_time = 0
controller.media_info.end_time = controller.media_info.length
# parse the metadata of the file
controller.vlc_media.parse()
controller.mediabar.seek_slider.setMinimum(controller.media_info.start_time)
@ -235,7 +215,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
self.volume(controller, get_volume(controller))
return True
def media_state_wait(self, controller: Type[SlideController], media_state: VlCState) -> bool:
def media_state_wait(self, controller: SlideController, media_state: VlCState) -> bool:
"""
Wait for the video to change its state
Wait no longer than 60 seconds. (loading an iso file needs a long time)
@ -254,19 +234,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
return False
return True
def resize(self, controller: Type[SlideController]) -> None:
"""
Resize the player
:param controller: The display where the media is stored within the controller.
:return:
"""
if controller.is_live:
controller.vlc_widget.setGeometry(ScreenList().current.display_geometry)
else:
controller.vlc_widget.resize(controller.preview_display.size())
def play(self, controller: Type[SlideController], output_display: Type[DisplayWindow]) -> bool:
def play(self, controller: SlideController, output_display: DisplayWindow) -> bool:
"""
Play the current item
@ -282,7 +250,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
self.set_state(MediaState.Playing, controller)
return True
def pause(self, controller: Type[SlideController]) -> None:
def pause(self, controller: SlideController) -> None:
"""
Pause the current item
@ -295,7 +263,7 @@ class VlcPlayer(MediaPlayer, LogMixin):
if self.media_state_wait(controller, VlCState.Paused):
self.set_state(MediaState.Paused, controller)
def stop(self, controller: Type[SlideController]) -> None:
def stop(self, controller: SlideController) -> None:
"""
Stop the current item
@ -304,53 +272,3 @@ class VlcPlayer(MediaPlayer, LogMixin):
"""
threading.Thread(target=controller.vlc_media_player.stop).start()
self.set_state(MediaState.Stopped, controller)
def volume(self, controller: Type[SlideController], vol: int) -> None:
"""
Set the volume
:param vol: The volume to be sets
:param controller: The controller where the media is
:return:
"""
controller.vlc_media_player.audio_set_volume(vol)
def seek(self, controller: Type[SlideController], seek_value: int) -> None:
"""
Go to a particular position
:param seek_value: The position of where a seek goes to
:param controller: The controller where the media is
"""
if controller.vlc_media_player.is_seekable():
controller.vlc_media_player.set_time(seek_value)
def reset(self, controller: Type[SlideController]) -> None:
"""
Reset the player
:param controller: The controller where the media is
"""
controller.vlc_media_player.stop()
self.set_state(MediaState.Off, controller)
def set_visible(self, controller: Type[SlideController], status: bool) -> None:
"""
Set the visibility
:param controller: The controller where the media display is
:param status: The visibility status
"""
controller.vlc_widget.setVisible(status)
def update_ui(self, controller: Type[SlideController], output_display: Type[DisplayWindow]) -> None:
"""
Update the UI
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
"""
if not controller.mediabar.seek_slider.isSliderDown():
controller.mediabar.seek_slider.blockSignals(True)
controller.mediabar.seek_slider.setSliderPosition(controller.vlc_media_player.get_time())
controller.mediabar.seek_slider.blockSignals(False)

View File

@ -0,0 +1,234 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2023 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/>. #
##########################################################################
"""
The :mod:`~openlp.core.ui.media.vlcplayer` module contains our VLC component wrapper
"""
import logging
import os
import threading
from datetime import datetime
from time import sleep
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin
from openlp.core.common.registry import Registry
from openlp.core.display.window import DisplayWindow
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.slidecontroller import SlideController
from openlp.core.ui.media import MediaState, VlCState, get_volume
from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.ui.media.vlcplayer import get_vlc
log = logging.getLogger(__name__)
STATE_WAIT_TIME = 60
def end_reached(event, controller: SlideController) -> None:
"""
Process the end of a Track, triggered by VLC
:param event: the vlc event triggered
:param controller: The controller upon which the event occurs
:return: None
"""
if controller.is_live:
Registry().get('media_controller').vlc_live_media_stop.emit()
else:
Registry().get('media_controller').vlc_preview_media_stop.emit()
def pos_callback(event, controller: SlideController) -> None:
"""
A Tick event triggered by VLC
:param event: The VLC Event triggered
:param controller: The controller upon which the event occurs
:return: None
"""
controller.media_info.timer = controller.vlc_media_player.get_time()
if controller.is_live:
Registry().get('media_controller').vlc_live_media_tick.emit()
else:
Registry().get('media_controller').vlc_preview_media_tick.emit()
class VlcPlayerPL(MediaPlayer, LogMixin):
"""
A specialised version of the MediaPlayer class, which provides a VLC display with a Playlist.
"""
def __init__(self, parent):
"""
Constructor
"""
super(VlcPlayerPL, self).__init__(parent, 'vlc')
self.original_name = 'VLC'
self.display_name = '&VLC'
self.parent = parent
self.can_folder = True
self.can_repeat = True
self.can_background = True
def setup(self, controller: SlideController, display: DisplayWindow) -> None:
"""
Set up the media player
:param controller: The controller where the media is
:param display: The display where the media is.
:return:
"""
vlc = get_vlc()
if controller.is_live:
controller.vlc_widget = QtWidgets.QFrame(controller)
controller.vlc_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowType.Tool |
QtCore.Qt.WindowStaysOnTopHint)
else:
controller.vlc_widget = QtWidgets.QFrame(display)
controller.vlc_widget.setFrameStyle(QtWidgets.QFrame.NoFrame)
# creating a basic vlc instance
command_line_options = '--no-video-title-show '
if self.settings.value('advanced/hide mouse') and controller.is_live:
command_line_options += '--mouse-hide-timeout=0 '
if self.settings.value('media/vlc arguments'):
options = command_line_options + ' ' + self.settings.value('media/vlc arguments')
controller.vlc_instance = vlc.Instance(options)
# if the instance is None, it is likely that the comamndline options were invalid, so try again without
if not controller.vlc_instance:
controller.vlc_instance = vlc.Instance(command_line_options)
if controller.vlc_instance:
critical_error_message_box(message=translate('MediaPlugin.VlcPlayer',
'The VLC arguments are invalid.'))
else:
return
else:
controller.vlc_instance = vlc.Instance(command_line_options)
if not controller.vlc_instance:
return
self.log_debug(f"VLC version: {vlc.libvlc_get_version()}")
# creating an empty vlc media player
self.has_own_widget = True
def check_available(self):
"""
Return the availability of VLC
"""
return get_vlc() is not None
def load(self, controller: SlideController, output_display: DisplayWindow, file: str) -> bool:
"""
Load a video into VLC
:param controller: The controller where the media is
:param output_display: The display where the media is
:param file: file/stream to be played
:return: Success or Failure
"""
self.log_debug('load video in VLC Controller')
if not controller.vlc_instance:
return False
# The media player moved here to clear the playlist between uses.
controller.vlc_media_player = controller.vlc_instance.media_player_new()
controller.vlc_widget.resize(controller.size())
controller.vlc_widget.hide()
self.add_display(controller)
path = os.path.normcase(file)
controller.vlc_media = controller.vlc_instance.media_list_new()
controller.vlc_media.add_media(path)
controller.vlc_media_listPlayer = controller.vlc_instance.media_list_player_new()
controller.vlc_media_listPlayer.set_media_player(controller.vlc_media_player)
controller.vlc_media_listPlayer.set_media_list(controller.vlc_media)
controller.vlc_events = controller.vlc_media_player.event_manager()
vlc = get_vlc()
controller.vlc_events.event_attach(vlc.EventType.MediaPlayerTimeChanged, pos_callback, controller)
controller.vlc_events.event_attach(vlc.EventType.MediaPlayerEndReached, end_reached, controller)
controller.media_info.start_time = 0
controller.media_info.end_time = controller.media_info.length
# parse the metadata of the file
controller.mediabar.seek_slider.setMinimum(controller.media_info.start_time)
controller.mediabar.seek_slider.setMaximum(controller.media_info.end_time)
self.volume(controller, get_volume(controller))
return True
def media_state_wait(self, controller: SlideController, media_state: VlCState) -> bool:
"""
Wait for the video to change its state
Wait no longer than 60 seconds. (loading an iso file needs a long time)
:param media_state: The state of the playing media
:param controller: The controller where the media is
:return:
"""
start = datetime.now()
while media_state != controller.vlc_media_listPlayer.get_state():
sleep(0.1)
if controller.vlc_media_listPlayer.get_state() == VlCState.Error:
return False
self.application.process_events()
if (datetime.now() - start).seconds > STATE_WAIT_TIME:
return False
return True
def toggle_loop(self, controller, loop_required: bool) -> None:
vlc = get_vlc()
if loop_required:
controller.vlc_media_listPlayer.set_playback_mode(vlc.PlaybackMode().loop)
else:
controller.vlc_media_listPlayer.set_playback_mode(vlc.PlaybackMode().default)
def play(self, controller: SlideController, output_display: DisplayWindow) -> bool:
"""
Play the current item
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
:return:
"""
self.log_debug('vlc play, mediatype: ' + str(controller.media_info.media_type))
threading.Thread(target=controller.vlc_media_listPlayer.play).start()
if not self.media_state_wait(controller, VlCState.Playing):
return False
self.volume(controller, get_volume(controller))
self.set_state(MediaState.Playing, controller)
return True
def pause(self, controller: SlideController) -> None:
"""
Pause the current item
:param controller: The controller which is managing the display
:return:
"""
if controller.vlc_media_listPlayer.get_state() != VlCState.Playing:
return
controller.vlc_media_listPlayer.pause()
if self.media_state_wait(controller, VlCState.Paused):
self.set_state(MediaState.Paused, controller)
def stop(self, controller: SlideController) -> None:
"""
Stop the current item
:param controller: The controller where the media is
:return:
"""
threading.Thread(target=controller.vlc_media_listPlayer.stop).start()
self.set_state(MediaState.Stopped, controller)

View File

@ -45,8 +45,9 @@ TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg'
@pytest.fixture
def media_env(registry):
"""Local test setup"""
def media_env(qapp, registry):
"""Local test setup - qapp need to allow tests to run standalone.
"""
Registry().register('service_manager', MagicMock())
media_controller = MediaController()
yield media_controller
@ -271,7 +272,7 @@ def test_resize(media_env):
mocked_display = MagicMock()
# WHEN: resize() is called
media_env.media_controller.resize(mocked_display, mocked_player)
media_env.media_controller._resize(mocked_display, mocked_player)
# THEN: The player's resize method should be called correctly
mocked_player.resize.assert_called_with(mocked_display)
@ -338,7 +339,7 @@ def test_check_file_video(media_env):
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.media_info.file_info = [TEST_PATH / 'mp3_file.mp3']
media_env.media_controller.current_media_players = {}
media_env.media_controller.vlc_player = MagicMock()
media_env.media_controller.vlc_playerpl = MagicMock()
# WHEN: calling _check_file_type when no players exists
ret = media_env.media_controller._check_file_type(mocked_controller, mocked_display)
@ -358,7 +359,7 @@ def test_check_file_audio(media_env):
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.media_info.file_info = [TEST_PATH / 'mp4_file.mp4']
media_env.media_controller.current_media_players = {}
media_env.media_controller.vlc_player = MagicMock()
media_env.media_controller.vlc_playerpl = MagicMock()
# WHEN: calling _check_file_type when no players exists
ret = media_env.media_controller._check_file_type(mocked_controller, mocked_display)
@ -747,7 +748,7 @@ def test_setup_display(MockItemMediaInfo, media_env):
media_env.media_controller.vlc_player = MagicMock()
mocked_display = MagicMock()
media_env.media_controller._define_display = MagicMock(return_value=mocked_display)
media_env.media_controller.vlc_player = MagicMock()
media_env.media_controller.vlc_playerpl = MagicMock()
controller = MagicMock()
# WHEN: setup_display() is called
@ -756,8 +757,8 @@ def test_setup_display(MockItemMediaInfo, media_env):
# THEN: The right calls should have been made
assert controller.media_info == mocked_media_info
assert controller.has_audio is False
media_env.media_controller._define_display.assert_called_once_with(controller)
media_env.media_controller.vlc_player.setup(controller, mocked_display, False)
media_env.media_controller._define_display.assert_called_with(controller)
media_env.media_controller.vlc_playerpl.setup(controller, mocked_display, False)
def test_media_play(media_env):
@ -812,6 +813,7 @@ def test_decide_autoplay_media_normal_hidden_live(media_env, settings):
settings.setValue('core/auto unblank', True)
settings.setValue('media/media auto start', QtCore.Qt.CheckState.Unchecked)
media_env.media_controller.is_live = True
media_env.media_controller.media_info = ItemMediaInfo()
media_env.media_controller.is_theme_background = True
# WHEN: decide_autoplay() is called
ret = media_env.media_controller.decide_autoplay(mocked_service_item, media_env.media_controller, HideMode.Theme)
@ -829,6 +831,7 @@ def test_decide_autoplay_media_normal_not_hidden_live(media_env, settings):
settings.setValue('core/auto unblank', False)
settings.setValue('media/media auto start', QtCore.Qt.CheckState.Unchecked)
media_env.media_controller.is_live = True
media_env.media_controller.media_info = ItemMediaInfo()
media_env.media_controller.is_theme_background = False
# WHEN: decide_autoplay() is called
ret = media_env.media_controller.decide_autoplay(mocked_service_item, media_env.media_controller, HideMode.Screen)
@ -847,6 +850,7 @@ def test_decide_autoplay_media_autostart_not_hidden_live(media_env, settings):
settings.setValue('core/auto unblank', False)
settings.setValue('media/media auto start', QtCore.Qt.CheckState.Unchecked)
media_env.media_controller.is_live = True
media_env.media_controller.media_info = ItemMediaInfo()
media_env.media_controller.is_theme_background = False
# WHEN: decide_autoplay() is called
ret = media_env.media_controller.decide_autoplay(mocked_service_item, media_env.media_controller, False)
@ -865,6 +869,7 @@ def test_decide_autoplay_media_global_autostart_not_hidden_live(media_env, setti
settings.setValue('core/auto unblank', False)
settings.setValue('media/media auto start', QtCore.Qt.CheckState.Checked)
media_env.media_controller.is_live = True
media_env.media_controller.media_info = ItemMediaInfo()
media_env.media_controller.is_theme_background = False
# WHEN: decide_autoplay() is called
ret = media_env.media_controller.decide_autoplay(mocked_service_item, media_env.media_controller, False)
@ -882,7 +887,8 @@ def test_decide_autoplay_media_normal_autounblank_live(media_env, settings):
settings.setValue('core/auto unblank', True)
settings.setValue('media/media auto start', QtCore.Qt.CheckState.Unchecked)
media_env.media_controller.is_live = True
media_env.media_controller.is_theme_background = False
media_env.media_controller.media_info = ItemMediaInfo()
media_env.media_controller.media_info.is_theme_background = False
# WHEN: decide_autoplay() is called
ret = media_env.media_controller.decide_autoplay(mocked_service_item, media_env.media_controller, HideMode.Screen)
# THEN: Autoplay will obey the following
@ -901,7 +907,8 @@ def test_media_bar_play(media_env, settings):
mocked_controller.mediabar.actions['playbackPause'] = MagicMock()
mocked_controller.mediabar.actions['playbackStop'] = MagicMock()
mocked_controller.mediabar.actions['playbackLoop'] = MagicMock()
media_env.is_theme_background = False
media_env.media_controller.current_media_players = MagicMock()
mocked_controller.media_info.is_theme_background = False
settings.setValue('media/live loop', False)
# WHEN: _media_bar() is called
media_env.media_controller._media_bar(mocked_controller, "play")
@ -924,7 +931,8 @@ def test_media_bar_stop(media_env, settings, mode):
mocked_controller.mediabar.actions['playbackPause'] = MagicMock()
mocked_controller.mediabar.actions['playbackStop'] = MagicMock()
mocked_controller.mediabar.actions['playbackLoop'] = MagicMock()
media_env.is_theme_background = False
media_env.media_controller.current_media_players = MagicMock()
mocked_controller.media_info.is_theme_background = False
settings.setValue('media/live loop', False)
# WHEN: _media_bar() is called
media_env.media_controller._media_bar(mocked_controller, mode)
@ -934,10 +942,10 @@ def test_media_bar_stop(media_env, settings, mode):
mocked_controller.mediabar.actions['playbackStop'].setDisabled.assert_called_with(False)
@pytest.mark.parametrize("back, stream, result", [(False, MediaType.Video, False),
(True, MediaType.Video, False),
(False, MediaType.Stream, True)])
def test_media_bar_loop_disabled(media_env, settings, back, stream, result):
@pytest.mark.parametrize("back, repeat, result", [(False, True, False),
(True, True, False),
(False, False, False)])
def test_media_bar_loop_disabled(media_env, settings, back, repeat, result):
"""
Test that media bar is set correctly following a list of events
"""
@ -945,14 +953,17 @@ def test_media_bar_loop_disabled(media_env, settings, back, stream, result):
mocked_controller = MagicMock()
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.is_live = True
mocked_controller.controller_type = 1 # Live
mocked_controller.media_info.is_background = back
mocked_controller.media_info.media_type = stream
mocked_controller.media_info.is_theme_background = False
mocked_controller.mediabar = OpenLPToolbar(None)
mocked_controller.mediabar.actions['playbackPlay'] = MagicMock()
mocked_controller.mediabar.actions['playbackPause'] = MagicMock()
mocked_controller.mediabar.actions['playbackStop'] = MagicMock()
mocked_controller.mediabar.actions['playbackLoop'] = MagicMock()
media_env.is_theme_background = False
mocked_vlc_player = MagicMock()
mocked_vlc_player.can_repeat.return_value = repeat
media_env.current_media_players = {1: mocked_vlc_player}
# WHEN: _media_bar() is called
media_env.media_controller._media_bar(mocked_controller, "load")
# THEN: The following functions should have been called
@ -968,14 +979,18 @@ def test_media_bar_loop_checked(media_env, settings, loop, result):
mocked_controller = MagicMock()
mocked_controller.media_info = ItemMediaInfo()
mocked_controller.is_live = True
mocked_controller.controller_type = 1 # Live
mocked_controller.media_info.is_background = True
mocked_controller.media_info.is_theme_background = False
mocked_controller.media_info.media_type = MediaType.Video
mocked_controller.mediabar = OpenLPToolbar(None)
mocked_controller.mediabar.actions['playbackPlay'] = MagicMock()
mocked_controller.mediabar.actions['playbackPause'] = MagicMock()
mocked_controller.mediabar.actions['playbackStop'] = MagicMock()
mocked_controller.mediabar.actions['playbackLoop'] = MagicMock()
media_env.is_theme_background = False
mocked_vlc_player = MagicMock()
mocked_vlc_player.can_repeat.return_value = True
media_env.current_media_players = {1: mocked_vlc_player}
settings.setValue('media/live loop', loop)
# WHEN: _media_bar() is called
media_env.media_controller._media_bar(mocked_controller, "load")

View File

@ -20,6 +20,7 @@
##########################################################################
"""
Package to test the openlp.core.ui.media.vlcplayer package.
This class is for DVD and Streaming using a Player.
"""
import os
import sys
@ -62,8 +63,8 @@ def test_init(mock_settings):
assert vlc_player.can_folder is True
@patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
@patch('openlp.core.ui.media.mediaplayer.is_win')
@patch('openlp.core.ui.media.mediaplayer.is_macosx')
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.QtWidgets')
def test_setup(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win, mock_settings):
@ -109,8 +110,8 @@ def test_setup(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win,
assert vlc_player.has_own_widget is True
@patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
@patch('openlp.core.ui.media.mediaplayer.is_win')
@patch('openlp.core.ui.media.mediaplayer.is_macosx')
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.QtWidgets')
def test_setup_has_audio(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win, mock_settings):
@ -144,8 +145,8 @@ def test_setup_has_audio(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mock
mocked_vlc.Instance.assert_called_with('--no-video-title-show --input-repeat=99999999 ')
@patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
@patch('openlp.core.ui.media.mediaplayer.is_win')
@patch('openlp.core.ui.media.mediaplayer.is_macosx')
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.QtWidgets')
def test_setup_visible_mouse(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win, mock_settings):
@ -179,8 +180,8 @@ def test_setup_visible_mouse(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx,
mocked_vlc.Instance.assert_called_with('--no-video-title-show --input-repeat=99999999 ')
@patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
@patch('openlp.core.ui.media.mediaplayer.is_win')
@patch('openlp.core.ui.media.mediaplayer.is_macosx')
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.QtWidgets')
def test_setup_windows(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win, mock_settings):
@ -214,8 +215,8 @@ def test_setup_windows(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked
mocked_media_player_new.set_hwnd.assert_called_with(2)
@patch('openlp.core.ui.media.vlcplayer.is_win')
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
@patch('openlp.core.ui.media.mediaplayer.is_win')
@patch('openlp.core.ui.media.mediaplayer.is_macosx')
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.QtWidgets')
def test_setup_osx(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win, mock_settings):
@ -283,7 +284,7 @@ def test_check_not_available(mocked_get_vlc):
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
@patch('openlp.core.ui.media.vlcplayer.os.path.normcase')
def test_load(mocked_normcase, mocked_get_vlc, settings):
def test_load_stream(mocked_normcase, mocked_get_vlc, settings):
"""
Test loading a video into VLC
"""
@ -294,24 +295,19 @@ def test_load(mocked_normcase, mocked_get_vlc, settings):
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_controller = MagicMock()
mocked_controller.media_info.media_type = MediaType.Video
mocked_controller.media_info.media_type = MediaType.Stream
mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
mocked_vlc_media = MagicMock()
mocked_controller.vlc_media = MagicMock()
mocked_media = MagicMock()
mocked_media.get_duration.return_value = 10000
mocked_controller.vlc_instance.media_new_path.return_value = mocked_vlc_media
mocked_controller.vlc_media_player.get_media.return_value = mocked_media
vlc_player = VlcPlayer(None)
# WHEN: A video is loaded into VLC
result = vlc_player.load(mocked_controller, mocked_display, media_path)
# THEN: The video should be loaded
mocked_normcase.assert_called_with(media_path)
mocked_controller.vlc_instance.media_new_path.assert_called_with(media_path)
assert mocked_vlc_media == mocked_controller.vlc_media
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
mocked_vlc_media.parse.assert_called_with()
mocked_controller.vlc_instance.media_new_path.assert_not_called()
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_controller.vlc_media)
mocked_controller.vlc_media.parse.assert_called_with()
assert result is True

View File

@ -0,0 +1,581 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2023 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/>. #
##########################################################################
"""
Package to test the openlp.core.ui.media.vlcplayerpl package.
This class is for Audio and Video only using a PlayList
"""
import os
import sys
from datetime import timedelta
from unittest.mock import MagicMock, call, patch
import pytest
from openlp.core.common.registry import Registry
from openlp.core.ui.media import MediaState, MediaType, VlCState
from openlp.core.ui.media.vlcplayerpl import VlcPlayerPL
from tests.helpers import MockDateTime
@pytest.fixture
def vlc_env():
"""Local test setup"""
if 'VLC_PLUGIN_PATH' in os.environ:
del os.environ['VLC_PLUGIN_PATH']
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
del sys.modules['openlp.core.ui.media.vendor.vlc']
yield
MockDateTime.revert()
def test_init(mock_settings):
"""
Test that the VLC player class initialises correctly
"""
# GIVEN: A mocked out list of extensions
# TODO: figure out how to mock out the lists of extensions
# WHEN: The VlcPlayer class is instantiated
vlc_player = VlcPlayerPL(None)
# THEN: The correct variables are set
assert 'VLC' == vlc_player.original_name
assert '&VLC' == vlc_player.display_name
assert vlc_player.parent is None
assert vlc_player.can_folder is True
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.QtWidgets')
def test_setup(MockedQtWidgets, mocked_get_vlc, mock_settings):
"""
Test the setup method
"""
# GIVEN: A bunch of mocked out stuff and a VlcPlayer object
mock_settings.value.return_value = ''
mocked_qframe = MagicMock()
mocked_qframe.winId.return_value = 2
MockedQtWidgets.QFrame.NoFrame = 1
MockedQtWidgets.QFrame.return_value = mocked_qframe
mocked_media_player_new = MagicMock()
mocked_instance = MagicMock()
mocked_instance.media_player_new.return_value = mocked_media_player_new
mocked_vlc = MagicMock()
mocked_vlc.Instance.return_value = mocked_instance
mocked_get_vlc.return_value = mocked_vlc
mocked_output_display = MagicMock()
mocked_controller = MagicMock()
mocked_controller.is_live = True
mocked_output_display.size.return_value = (10, 10)
vlc_player = VlcPlayerPL(None)
# WHEN: setup() is run
vlc_player.setup(mocked_output_display, mocked_controller)
# THEN: The VLC widget should be set up correctly
assert mocked_output_display.vlc_widget == mocked_qframe
mocked_qframe.setFrameStyle.assert_called_with(1)
mock_settings.value.assert_any_call('advanced/hide mouse')
mock_settings.value.assert_any_call('media/vlc arguments')
mocked_vlc.Instance.assert_called_with('--no-video-title-show ')
assert mocked_output_display.vlc_instance == mocked_instance
assert vlc_player.has_own_widget is True
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.QtWidgets')
def test_setup_has_audio(MockedQtWidgets, mocked_get_vlc, mock_settings):
"""
Test the setup method when has_audio is True
"""
# GIVEN: A bunch of mocked out stuff and a VlcPlayer object
mock_settings.value.return_value = ''
mocked_qframe = MagicMock()
mocked_qframe.winId.return_value = 2
MockedQtWidgets.QFrame.NoFrame = 1
MockedQtWidgets.QFrame.return_value = mocked_qframe
mocked_media_player_new = MagicMock()
mocked_instance = MagicMock()
mocked_instance.media_player_new.return_value = mocked_media_player_new
mocked_vlc = MagicMock()
mocked_vlc.Instance.return_value = mocked_instance
mocked_get_vlc.return_value = mocked_vlc
mocked_output_display = MagicMock()
mocked_controller = MagicMock()
mocked_controller.is_live = True
mocked_output_display.size.return_value = (10, 10)
vlc_player = VlcPlayerPL(None)
# WHEN: setup() is run
vlc_player.setup(mocked_output_display, mocked_controller)
# THEN: The VLC instance should be created with the correct options
mocked_vlc.Instance.assert_called_with('--no-video-title-show ')
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.QtWidgets')
def test_setup_visible_mouse(MockedQtWidgets, mocked_get_vlc, mock_settings):
"""
Test the setup method when Settings().value("hide mouse") is False
"""
# GIVEN: A bunch of mocked out stuff and a VlcPlayer object
mock_settings.value.return_value = ''
mocked_qframe = MagicMock()
mocked_qframe.winId.return_value = 2
MockedQtWidgets.QFrame.NoFrame = 1
MockedQtWidgets.QFrame.return_value = mocked_qframe
mocked_media_player_new = MagicMock()
mocked_instance = MagicMock()
mocked_instance.media_player_new.return_value = mocked_media_player_new
mocked_vlc = MagicMock()
mocked_vlc.Instance.return_value = mocked_instance
mocked_get_vlc.return_value = mocked_vlc
mocked_output_display = MagicMock()
mocked_controller = MagicMock()
mocked_controller.is_live = True
mocked_output_display.size.return_value = (10, 10)
vlc_player = VlcPlayerPL(None)
# WHEN: setup() is run
vlc_player.setup(mocked_output_display, mocked_controller)
# THEN: The VLC instance should be created with the correct options
mocked_vlc.Instance.assert_called_with('--no-video-title-show ')
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_check_available(mocked_get_vlc):
"""
Check that when the "vlc" module is available, then VLC is set as available
"""
# GIVEN: A mocked out get_vlc() method and a VlcPlayer instance
mocked_get_vlc.return_value = MagicMock()
vlc_player = VlcPlayerPL(None)
# WHEN: vlc
is_available = vlc_player.check_available()
# THEN: VLC should be available
assert is_available is True
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_check_not_available(mocked_get_vlc):
"""
Check that when the "vlc" module is not available, then VLC is set as unavailable
"""
# GIVEN: A mocked out get_vlc() method and a VlcPlayer instance
mocked_get_vlc.return_value = None
vlc_player = VlcPlayerPL(None)
# WHEN: vlc
is_available = vlc_player.check_available()
# THEN: VLC should NOT be available
assert is_available is False
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.os.path.normcase')
def test_load(mocked_normcase, mocked_get_vlc, settings):
"""
Test loading a video into VLC
"""
# GIVEN: A mocked out get_vlc() method
media_path = '/path/to/media.mp4'
mocked_normcase.side_effect = lambda x: x
mocked_vlc = MagicMock()
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_controller = MagicMock()
mocked_controller.media_info.media_type = MediaType.Video
mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
mocked_vlc_media = MagicMock()
mocked_media = MagicMock()
mocked_media.get_duration.return_value = 10000
mocked_controller.vlc_instance.media_new_path.return_value = mocked_vlc_media
mocked_controller.vlc_media_player.get_media.return_value = mocked_media
vlc_player = VlcPlayerPL(None)
# WHEN: A video is loaded into VLC
result = vlc_player.load(mocked_controller, mocked_display, media_path)
# THEN: The video should be loaded
mocked_normcase.assert_called_with(media_path)
mocked_controller.vlc_instance.media_player_new.assert_called()
mocked_controller.vlc_media.add_media.assert_called_with(media_path)
assert result is True
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.datetime', MockDateTime)
def test_media_state_wait(mocked_get_vlc):
"""
Check that waiting for a state change works
"""
# GIVEN: A mocked out get_vlc method
mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc
mocked_controller = MagicMock()
mocked_controller.vlc_media_listPlayer.get_state.return_value = VlCState.Buffering
Registry.create()
mocked_application = MagicMock()
Registry().register('application', mocked_application)
vlc_player = VlcPlayerPL(None)
# WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_controller, VlCState.Buffering)
# THEN: The results should be True
assert result is True
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.datetime', MockDateTime)
def test_media_state_wait_error(mocked_get_vlc, vlc_env):
"""
Check that getting an error when waiting for a state change returns False
"""
# GIVEN: A mocked out get_vlc method
mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc
mocked_controller = MagicMock()
mocked_controller.vlc_media_listPlayer.get_state.return_value = VlCState.Error
Registry.create()
mocked_application = MagicMock()
Registry().register('application', mocked_application)
vlc_player = VlcPlayerPL(None)
# WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_controller, VlCState.Buffering)
# THEN: The results should be True
assert result is False
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
@patch('openlp.core.ui.media.vlcplayerpl.datetime', MockDateTime)
def test_media_state_wait_times_out(mocked_get_vlc, vlc_env):
"""
Check that waiting for a state returns False when it times out after 60 seconds
"""
# GIVEN: A mocked out get_vlc method
timeout = MockDateTime.return_values[0] + timedelta(seconds=61)
MockDateTime.return_values.append(timeout)
mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc
mocked_controller = MagicMock()
mocked_controller.vlc_media_listPlayer.get_state.return_value = VlCState.Buffering
Registry.create()
mocked_application = MagicMock()
Registry().register('application', mocked_application)
vlc_player = VlcPlayerPL(None)
# WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_controller, VlCState.Playing)
# THEN: The results should be True
assert result is False
def test_resize():
"""
Test resizing the player
"""
# GIVEN: A display object and a VlcPlayer instance
mocked_controller = MagicMock()
mocked_controller.preview_display.size.return_value = (10, 10)
mocked_controller.is_live = False
vlc_player = VlcPlayerPL(None)
# WHEN: resize is called
vlc_player.resize(mocked_controller)
# THEN: The right methods should have been called
mocked_controller.preview_display.size.assert_called_with()
mocked_controller.vlc_widget.resize.assert_called_with((10, 10))
@patch('openlp.core.ui.media.vlcplayerpl.threading')
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_play(mocked_get_vlc, mocked_threading, settings):
"""
Test the play() method
"""
# GIVEN: A bunch of mocked out things
mocked_thread = MagicMock()
mocked_threading.Thread.return_value = mocked_thread
mocked_vlc = MagicMock()
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_controller = MagicMock()
mocked_media = MagicMock()
mocked_controller.vlc_media_player.get_media.return_value = mocked_media
vlc_player = VlcPlayerPL(None)
vlc_player.set_state(MediaState.Paused, mocked_controller)
# WHEN: play() is called
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
mocked_media_state_wait.return_value = True
result = vlc_player.play(mocked_controller, mocked_display)
# THEN: A bunch of things should happen to play the media
mocked_thread.start.assert_called_with()
assert MediaState.Playing == vlc_player.get_live_state()
assert result is True, 'The value returned from play() should be True'
@patch('openlp.core.ui.media.vlcplayerpl.threading')
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_play_media_wait_state_not_playing(mocked_get_vlc, mocked_threading):
"""
Test the play() method when media_wait_state() returns False
"""
# GIVEN: A bunch of mocked out things
mocked_thread = MagicMock()
mocked_threading.Thread.return_value = mocked_thread
mocked_vlc = MagicMock()
mocked_get_vlc.return_value = mocked_vlc
mocked_controller = MagicMock()
mocked_controller.media_info.start_time = 0
mocked_output_display = MagicMock()
vlc_player = VlcPlayerPL(None)
vlc_player.set_state(MediaState.Paused, mocked_output_display)
# WHEN: play() is called
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \
patch.object(vlc_player, 'volume'):
mocked_media_state_wait.return_value = False
result = vlc_player.play(mocked_controller, mocked_output_display)
# THEN: A thread should be started, but the method should return False
mocked_thread.start.assert_called_with()
assert result is False
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_pause(mocked_get_vlc):
"""
Test that the pause method works correctly
"""
# GIVEN: A mocked out get_vlc method
mocked_vlc = MagicMock()
mocked_vlc.State.Playing = VlCState.Playing
mocked_vlc.State.Paused = VlCState.Paused
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_display.vlc_media_listPlayer.get_state.return_value = VlCState.Playing
vlc_player = VlcPlayerPL(None)
# WHEN: The media is paused
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
mocked_media_state_wait.return_value = True
vlc_player.pause(mocked_display)
# THEN: The pause method should exit early
mocked_display.vlc_media_listPlayer.get_state.assert_called_with()
mocked_display.vlc_media_listPlayer.pause.assert_called_with()
mocked_media_state_wait.assert_called_with(mocked_display, VlCState.Paused)
assert MediaState.Paused == vlc_player.get_live_state()
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_pause_not_playing(mocked_get_vlc):
"""
Test the pause method when the player is not playing
"""
# GIVEN: A mocked out get_vlc method
mocked_vlc = MagicMock()
mocked_vlc.State.Playing = 1
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_display.vlc_media.get_state.return_value = VlCState.Paused
vlc_player = VlcPlayerPL(None)
# WHEN: The media is paused
vlc_player.pause(mocked_display)
# THEN: The pause method should exit early
mocked_display.vlc_media_listPlayer.get_state.assert_called_with()
assert 0 == mocked_display.vlc_media_player.pause.call_count
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_pause_fail(mocked_get_vlc):
"""
Test the pause method when the player fails to pause the media
"""
# GIVEN: A mocked out get_vlc method
mocked_vlc = MagicMock()
mocked_vlc.State.Playing = 1
mocked_vlc.State.Paused = 2
mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock()
mocked_display.vlc_media_listPlayer.get_state.return_value = VlCState.Playing
vlc_player = VlcPlayerPL(None)
# WHEN: The media is paused
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
mocked_media_state_wait.return_value = False
vlc_player.pause(mocked_display)
# THEN: The pause method should exit early
mocked_display.vlc_media_listPlayer.get_state.assert_called_with()
mocked_display.vlc_media_listPlayer.pause.assert_called_with()
mocked_media_state_wait.assert_called_with(mocked_display, VlCState.Paused)
assert MediaState.Paused is not vlc_player.state
@patch('openlp.core.ui.media.vlcplayerpl.threading')
def test_stop(mocked_threading):
"""
Test stopping the current item
"""
# GIVEN: A display object and a VlcPlayer instance and some mocked threads
mocked_thread = MagicMock()
mocked_threading.Thread.return_value = mocked_thread
mocked_stop = MagicMock()
mocked_display = MagicMock()
mocked_display.vlc_media_listPlayer.stop = mocked_stop
vlc_player = VlcPlayerPL(None)
# WHEN: stop is called
vlc_player.stop(mocked_display)
# THEN: A thread should have been started to stop VLC
mocked_threading.Thread.assert_called_with(target=mocked_stop)
mocked_thread.start.assert_called_with()
assert MediaState.Stopped == vlc_player.get_live_state()
def test_volume():
"""
Test setting the volume
"""
# GIVEN: A display object and a VlcPlayer instance
mocked_display = MagicMock()
mocked_display.has_audio = True
vlc_player = VlcPlayerPL(None)
# WHEN: The volume is set
vlc_player.volume(mocked_display, 10)
# THEN: The volume should have been set
mocked_display.vlc_media_player.audio_set_volume.assert_called_with(10)
def test_seek_unseekable_media():
"""
Test seeking something that can't be seeked
"""
# GIVEN: Unseekable media
mocked_display = MagicMock()
mocked_display.controller.media_info.media_type = MediaType.Audio
mocked_display.vlc_media_player.is_seekable.return_value = False
vlc_player = VlcPlayerPL(None)
# WHEN: seek() is called
vlc_player.seek(mocked_display, 100)
# THEN: nothing should happen
mocked_display.vlc_media_player.is_seekable.assert_called_with()
assert 0 == mocked_display.vlc_media_player.set_time.call_count
def test_seek_seekable_media():
"""
Test seeking something that is seekable, but not a DVD
"""
# GIVEN: Unseekable media
mocked_display = MagicMock()
mocked_display.controller.media_info.media_type = MediaType.Audio
mocked_display.vlc_media_player.is_seekable.return_value = True
vlc_player = VlcPlayerPL(None)
# WHEN: seek() is called
vlc_player.seek(mocked_display, 100)
# THEN: nothing should happen
mocked_display.vlc_media_player.is_seekable.assert_called_with()
mocked_display.vlc_media_player.set_time.assert_called_with(100)
def test_reset():
"""
Test the reset() method
"""
# GIVEN: Some mocked out stuff
mocked_display = MagicMock()
vlc_player = VlcPlayerPL(None)
# WHEN: reset() is called
vlc_player.reset(mocked_display)
# THEN: The media should be stopped and invisible
mocked_display.vlc_media_player.stop.assert_called_with()
mocked_display.vlc_widget.setVisible.assert_not_called()
assert MediaState.Off == vlc_player.get_live_state()
def test_set_visible_has_own_widget():
"""
Test the set_visible() method when the player has its own widget
"""
# GIVEN: Some mocked out stuff
mocked_display = MagicMock()
vlc_player = VlcPlayerPL(None)
vlc_player.has_own_widget = True
# WHEN: reset() is called
vlc_player.set_visible(mocked_display, True)
# THEN: The media should be stopped and invsibile
mocked_display.vlc_widget.setVisible.assert_called_with(True)
@patch('openlp.core.ui.media.vlcplayerpl.get_vlc')
def test_update_ui(mocked_get_vlc):
"""
Test updating the UI
"""
# GIVEN: A whole bunch of mocks
mocked_vlc = MagicMock()
mocked_vlc.State.Ended = 1
mocked_get_vlc.return_value = mocked_vlc
mocked_controller = MagicMock()
mocked_controller.media_info.end_time = 300
mocked_controller.mediabar.seek_slider.isSliderDown.return_value = False
mocked_display = MagicMock()
mocked_controller.vlc_media.get_state.return_value = 1
mocked_controller.vlc_media_player.get_time.return_value = 400000
vlc_player = VlcPlayerPL(None)
# WHEN: update_ui() is called
vlc_player.update_ui(mocked_controller, mocked_display)
# THEN: Certain methods should be called
mocked_controller.vlc_media_player.get_time.assert_called_with()
mocked_controller.mediabar.seek_slider.setSliderPosition.assert_called_with(400000)
expected_calls = [call(True), call(False)]
assert expected_calls == mocked_controller.mediabar.seek_slider.blockSignals.call_args_list