2017-11-07 03:32:40 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
# Copyright (c) 2008-2017 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 #
|
|
|
|
###############################################################################
|
|
|
|
"""
|
|
|
|
The :mod:`~openlp.core.display.window` module contains the display window
|
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import json
|
2018-03-25 00:31:12 +00:00
|
|
|
from inspect import currentframe, getouterframes
|
2017-11-07 03:32:40 +00:00
|
|
|
|
|
|
|
from PyQt5 import QtCore, QtWidgets, QtWebChannel
|
|
|
|
|
2018-03-24 07:19:06 +00:00
|
|
|
from openlp.core.common.path import Path, path_to_str
|
|
|
|
|
2017-11-07 03:32:40 +00:00
|
|
|
log = logging.getLogger(__name__)
|
2018-03-24 07:19:06 +00:00
|
|
|
DISPLAY_PATH = Path(__file__).parent / 'html' / 'display.html'
|
2017-11-07 03:32:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
class MediaWatcher(QtCore.QObject):
|
|
|
|
"""
|
|
|
|
A class to watch media events in the display and emit signals for OpenLP
|
|
|
|
"""
|
|
|
|
progress = QtCore.pyqtSignal(float)
|
|
|
|
duration = QtCore.pyqtSignal(float)
|
|
|
|
volume = QtCore.pyqtSignal(float)
|
|
|
|
playback_rate = QtCore.pyqtSignal(float)
|
|
|
|
ended = QtCore.pyqtSignal(bool)
|
|
|
|
muted = QtCore.pyqtSignal(bool)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(float)
|
|
|
|
def update_progress(self, time):
|
|
|
|
"""
|
|
|
|
Notify about the current position of the media
|
|
|
|
"""
|
|
|
|
log.warning(time)
|
|
|
|
self.progress.emit(time)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(float)
|
|
|
|
def update_duration(self, time):
|
|
|
|
"""
|
|
|
|
Notify about the duration of the media
|
|
|
|
"""
|
|
|
|
log.warning(time)
|
|
|
|
self.duration.emit(time)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(float)
|
|
|
|
def update_volume(self, level):
|
|
|
|
"""
|
|
|
|
Notify about the volume of the media
|
|
|
|
"""
|
|
|
|
log.warning(level)
|
|
|
|
level = level * 100
|
|
|
|
self.volume.emit(level)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(float)
|
|
|
|
def update_playback_rate(self, rate):
|
|
|
|
"""
|
|
|
|
Notify about the playback rate of the media
|
|
|
|
"""
|
|
|
|
log.warning(rate)
|
|
|
|
self.playback_rate.emit(rate)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
|
|
def has_ended(self, is_ended):
|
|
|
|
"""
|
|
|
|
Notify that the media has ended playing
|
|
|
|
"""
|
|
|
|
log.warning(is_ended)
|
|
|
|
self.ended.emit(is_ended)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot(bool)
|
|
|
|
def has_muted(self, is_muted):
|
|
|
|
"""
|
|
|
|
Notify that the media has been muted
|
|
|
|
"""
|
|
|
|
log.warning(is_muted)
|
|
|
|
self.muted.emit(is_muted)
|
|
|
|
|
|
|
|
|
|
|
|
class DisplayWindow(QtWidgets.QWidget):
|
|
|
|
"""
|
|
|
|
This is a window to show the output
|
|
|
|
"""
|
2017-12-01 21:53:32 +00:00
|
|
|
def __init__(self, parent=None, screen=None):
|
2017-11-07 03:32:40 +00:00
|
|
|
"""
|
|
|
|
Create the display window
|
|
|
|
"""
|
|
|
|
super(DisplayWindow, self).__init__(parent)
|
|
|
|
# Need to import this inline to get around a QtWebEngine issue
|
|
|
|
from openlp.core.display.webengine import WebEngineView
|
|
|
|
self._is_initialised = False
|
|
|
|
self._fbo = None
|
|
|
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint)
|
|
|
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
|
|
|
self.layout = QtWidgets.QVBoxLayout(self)
|
|
|
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.webview = WebEngineView(self)
|
|
|
|
self.layout.addWidget(self.webview)
|
|
|
|
self.webview.loadFinished.connect(self.after_loaded)
|
2018-03-24 07:19:06 +00:00
|
|
|
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(DISPLAY_PATH)))
|
2017-11-07 03:32:40 +00:00
|
|
|
self.media_watcher = MediaWatcher(self)
|
|
|
|
self.channel = QtWebChannel.QWebChannel(self)
|
|
|
|
self.channel.registerObject('mediaWatcher', self.media_watcher)
|
|
|
|
self.webview.page().setWebChannel(self.channel)
|
2017-12-01 21:53:32 +00:00
|
|
|
if screen and screen.is_display:
|
|
|
|
self.update_from_screen(screen)
|
2018-03-25 00:31:12 +00:00
|
|
|
print(getouterframes(currentframe(), 2)[1][3])
|
2017-12-01 21:53:32 +00:00
|
|
|
|
|
|
|
def update_from_screen(self, screen):
|
|
|
|
"""
|
|
|
|
Update the number and the geometry from the screen.
|
|
|
|
|
|
|
|
:param Screen screen: A `~openlp.core.display.screens.Screen` instance
|
|
|
|
"""
|
|
|
|
self.setGeometry(screen.display_geometry)
|
|
|
|
self.screen_number = screen.number
|
2017-11-07 03:32:40 +00:00
|
|
|
|
|
|
|
def set_url(self, url):
|
|
|
|
"""
|
|
|
|
Set the URL of the webview
|
2017-12-01 21:53:32 +00:00
|
|
|
|
|
|
|
:param str url: The URL to set
|
2017-11-07 03:32:40 +00:00
|
|
|
"""
|
|
|
|
if not isinstance(url, QtCore.QUrl):
|
|
|
|
url = QtCore.QUrl(url)
|
|
|
|
self.webview.setUrl(url)
|
|
|
|
|
|
|
|
def set_html(self, html):
|
|
|
|
"""
|
|
|
|
Set the html
|
|
|
|
"""
|
|
|
|
self.webview.setHtml(html)
|
|
|
|
|
|
|
|
def after_loaded(self):
|
|
|
|
"""
|
|
|
|
Add stuff after page initialisation
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.init();')
|
|
|
|
|
|
|
|
def run_javascript(self, script, is_sync=False):
|
|
|
|
"""
|
|
|
|
Run some Javascript in the WebView
|
|
|
|
|
|
|
|
:param script: The script to run, a string
|
|
|
|
:param is_sync: Run the script synchronously. Defaults to False
|
|
|
|
"""
|
|
|
|
if not is_sync:
|
|
|
|
self.webview.page().runJavaScript(script)
|
|
|
|
else:
|
|
|
|
self.__script_done = False
|
|
|
|
self.__script_result = None
|
|
|
|
|
|
|
|
def handle_result(result):
|
|
|
|
"""
|
|
|
|
Handle the result from the asynchronous call
|
|
|
|
"""
|
|
|
|
self.__script_done = True
|
|
|
|
self.__script_result = result
|
|
|
|
|
|
|
|
self.webview.page().runJavaScript(script, handle_result)
|
|
|
|
while not self.__script_done:
|
|
|
|
# TODO: Figure out how to break out of a potentially infinite loop
|
|
|
|
QtWidgets.QApplication.instance().processEvents()
|
|
|
|
return self.__script_result
|
|
|
|
|
|
|
|
def load_verses(self, verses):
|
|
|
|
"""
|
|
|
|
Set verses in the display
|
|
|
|
"""
|
|
|
|
json_verses = json.dumps(verses)
|
|
|
|
self.run_javascript('Display.setTextSlides({verses});'.format(verses=json_verses))
|
|
|
|
|
|
|
|
def load_images(self, images):
|
|
|
|
"""
|
|
|
|
Set images in the display
|
|
|
|
"""
|
|
|
|
for image in images:
|
|
|
|
if not image['file'].startswith('file://'):
|
|
|
|
image['file'] = 'file://' + image['file']
|
|
|
|
json_images = json.dumps(images)
|
|
|
|
self.run_javascript('Display.setImageSlides({images});'.format(images=json_images))
|
|
|
|
|
|
|
|
def load_video(self, video):
|
|
|
|
"""
|
|
|
|
Load video in the display
|
|
|
|
"""
|
|
|
|
if not video['file'].startswith('file://'):
|
|
|
|
video['file'] = 'file://' + video['file']
|
|
|
|
json_video = json.dumps(video)
|
|
|
|
self.run_javascript('Display.setVideo({video});'.format(video=json_video))
|
|
|
|
|
|
|
|
def play_video(self):
|
|
|
|
"""
|
|
|
|
Play the currently loaded video
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.playVideo();')
|
|
|
|
|
|
|
|
def pause_video(self):
|
|
|
|
"""
|
|
|
|
Pause the currently playing video
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.pauseVideo();')
|
|
|
|
|
|
|
|
def stop_video(self):
|
|
|
|
"""
|
|
|
|
Stop the currently playing video
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.stopVideo();')
|
|
|
|
|
|
|
|
def set_video_playback_rate(self, rate):
|
|
|
|
"""
|
|
|
|
Set the playback rate of the current video.
|
|
|
|
|
|
|
|
The rate can be any valid float, with 0.0 being stopped, 1.0 being normal speed,
|
|
|
|
over 1.0 is faster, under 1.0 is slower, and negative is backwards.
|
|
|
|
|
|
|
|
:param rate: A float indicating the playback rate.
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.setPlaybackRate({rate});'.format(rate))
|
|
|
|
|
|
|
|
def set_video_volume(self, level):
|
|
|
|
"""
|
|
|
|
Set the volume of the current video.
|
|
|
|
|
|
|
|
The volume should be an int from 0 to 100, where 0 is no sound and 100 is maximum volume. Any
|
|
|
|
values outside this range will raise a ``ValueError``.
|
|
|
|
|
|
|
|
:param level: A number between 0 and 100
|
|
|
|
"""
|
|
|
|
if level < 0 or level > 100:
|
|
|
|
raise ValueError('Volume should be from 0 to 100, was "{}"'.format(level))
|
|
|
|
self.run_javascript('Display.setVideoVolume({level});'.format(level))
|
|
|
|
|
|
|
|
def toggle_video_mute(self):
|
|
|
|
"""
|
|
|
|
Toggle the mute of the current video
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.toggleVideoMute();')
|
|
|
|
|
|
|
|
def save_screenshot(self, fname=None):
|
|
|
|
"""
|
|
|
|
Save a screenshot, either returning it or saving it to file
|
|
|
|
"""
|
|
|
|
pixmap = self.grab()
|
|
|
|
if fname:
|
|
|
|
ext = os.path.splitext(fname)[-1][1:]
|
|
|
|
pixmap.save(fname, ext)
|
|
|
|
else:
|
|
|
|
return pixmap
|
|
|
|
|
|
|
|
def set_theme(self, theme):
|
|
|
|
"""
|
|
|
|
Set the theme of the display
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.setTheme({theme});'.format(theme=theme.export_theme()))
|
|
|
|
|
|
|
|
def get_video_types(self):
|
|
|
|
"""
|
|
|
|
Get the types of videos playable by the embedded media player
|
|
|
|
"""
|
|
|
|
return self.run_javascript('Display.getVideoTypes();', is_sync=True)
|
2018-03-25 00:31:12 +00:00
|
|
|
|
|
|
|
def show_display(self):
|
|
|
|
"""
|
|
|
|
Show the display
|
|
|
|
"""
|
|
|
|
self.run_javascript('Display.show();')
|