openlp/openlp/core/ui/media/vlcplayer.py

425 lines
19 KiB
Python
Raw Normal View History

2011-07-10 21:43:07 +00:00
# -*- coding: utf-8 -*-
2012-12-11 19:55:47 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
2011-07-10 21:43:07 +00:00
2019-04-13 13:00:22 +00:00
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 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
"""
2017-12-28 08:27:44 +00:00
import ctypes
2011-07-10 21:43:07 +00:00
import logging
2012-04-28 11:13:16 +00:00
import os
2015-03-18 22:04:30 +00:00
import sys
2017-12-28 08:27:44 +00:00
import threading
from datetime import datetime
from distutils.version import LooseVersion
2015-11-07 00:49:40 +00:00
from PyQt5 import QtWidgets
2012-04-28 11:13:16 +00:00
2018-10-02 04:39:42 +00:00
from openlp.core.common import is_linux, is_macosx, is_win
2017-10-07 07:05:07 +00:00
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings
from openlp.core.ui.media import MediaState, MediaType
2012-09-09 06:06:44 +00:00
from openlp.core.ui.media.mediaplayer import MediaPlayer
2018-10-02 04:39:42 +00:00
log = logging.getLogger(__name__)
# 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']
2011-11-02 20:27:53 +00:00
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']
2011-11-02 20:27:53 +00:00
def get_vlc():
"""
In order to make this module more testable, we have to wrap the VLC import inside a method. We do this so that we
can mock out the VLC module entirely.
:return: The "vlc" module, or None
"""
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
# If VLC has already been imported, no need to do all the stuff below again
2018-03-18 14:56:02 +00:00
is_vlc_available = False
try:
is_vlc_available = bool(sys.modules['openlp.core.ui.media.vendor.vlc'].get_default_instance())
2018-10-27 01:40:20 +00:00
except Exception:
2018-03-18 14:56:02 +00:00
pass
if is_vlc_available:
return sys.modules['openlp.core.ui.media.vendor.vlc']
else:
return None
is_vlc_available = False
try:
if is_macosx():
# Newer versions of VLC on OS X need this. See https://forum.videolan.org/viewtopic.php?t=124521
os.environ['VLC_PLUGIN_PATH'] = '/Applications/VLC.app/Contents/MacOS/plugins'
# On Windows when frozen in PyInstaller, we need to blank SetDllDirectoryW to allow loading of the VLC dll.
2018-10-20 14:41:32 +00:00
# This is due to limitations (by design) in PyInstaller. SetDllDirectoryW original value is restored once
2015-12-26 14:30:12 +00:00
# VLC has been imported.
if is_win():
buffer_size = 1024
dll_directory = ctypes.create_unicode_buffer(buffer_size)
new_buffer_size = ctypes.windll.kernel32.GetDllDirectoryW(buffer_size, dll_directory)
dll_directory = ''.join(dll_directory[:new_buffer_size]).replace('\0', '')
log.debug('Original DllDirectory: %s' % dll_directory)
ctypes.windll.kernel32.SetDllDirectoryW(None)
from openlp.core.ui.media.vendor import vlc
if is_win():
ctypes.windll.kernel32.SetDllDirectoryW(dll_directory)
is_vlc_available = bool(vlc.get_default_instance())
except (ImportError, NameError, NotImplementedError):
pass
except OSError as e:
2018-03-18 14:56:02 +00:00
# this will get raised the first time
if is_win():
if not isinstance(e, WindowsError) and e.winerror != 126:
raise
else:
pass
if is_vlc_available:
try:
VERSION = vlc.libvlc_get_version().decode('UTF-8')
2018-10-27 01:40:20 +00:00
except Exception:
VERSION = '0.0.0'
# LooseVersion does not work when a string contains letter and digits (e. g. 2.0.5 Twoflower).
# http://bugs.python.org/issue14894
if LooseVersion(VERSION.split()[0]) < LooseVersion('1.1.0'):
is_vlc_available = False
log.debug('VLC could not be loaded, because the vlc version is too old: %s' % VERSION)
if is_vlc_available:
return vlc
else:
return None
# On linux we need to initialise X threads, but not when running tests.
# This needs to happen on module load and not in get_vlc(), otherwise it can cause crashes on some DE on some setups
# (reported on Gnome3, Unity, Cinnamon, all GTK+ based) when using native filedialogs...
if is_linux() and 'nose' not in sys.argv[0] and get_vlc():
try:
try:
x11 = ctypes.cdll.LoadLibrary('libX11.so.6')
except OSError:
# If libx11.so.6 was not found, fallback to more generic libx11.so
x11 = ctypes.cdll.LoadLibrary('libX11.so')
x11.XInitThreads()
2018-10-27 01:40:20 +00:00
except Exception:
log.exception('Failed to run XInitThreads(), VLC might not work properly!')
2011-11-11 16:45:25 +00:00
class VlcPlayer(MediaPlayer):
2011-07-10 21:43:07 +00:00
"""
2013-07-18 14:19:01 +00:00
A specialised version of the MediaPlayer class, which provides a VLC display.
2011-07-10 21:43:07 +00:00
"""
2011-09-22 18:22:35 +00:00
2011-07-10 21:43:07 +00:00
def __init__(self, parent):
"""
Constructor
"""
2013-08-31 18:17:38 +00:00
super(VlcPlayer, self).__init__(parent, 'vlc')
self.original_name = 'VLC'
self.display_name = '&VLC'
2011-07-10 21:43:07 +00:00
self.parent = parent
2013-03-06 23:00:57 +00:00
self.can_folder = True
2011-11-02 20:27:53 +00:00
self.audio_extensions_list = AUDIO_EXT
self.video_extensions_list = VIDEO_EXT
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def setup(self, output_display, live_display):
"""
Set up the media player
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
:param live_display: Is the display a live one.
2016-01-01 20:39:26 +00:00
:return:
"""
vlc = get_vlc()
2019-03-24 07:53:19 +00:00
output_display.vlc_widget = QtWidgets.QFrame(output_display)
output_display.vlc_widget.setFrameStyle(QtWidgets.QFrame.NoFrame)
2011-07-10 21:43:07 +00:00
# creating a basic vlc instance
2013-08-31 18:17:38 +00:00
command_line_options = '--no-video-title-show'
2019-03-24 07:53:19 +00:00
if Settings().value('advanced/hide mouse') and live_display:
2013-08-31 18:17:38 +00:00
command_line_options += ' --mouse-hide-timeout=0'
2019-03-24 07:53:19 +00:00
output_display.vlc_instance = vlc.Instance(command_line_options)
2011-07-10 21:43:07 +00:00
# creating an empty vlc media player
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player = output_display.vlc_instance.media_player_new()
output_display.vlc_widget.resize(output_display.size())
output_display.vlc_widget.raise_()
output_display.vlc_widget.hide()
# The media player has to be 'connected' to the QFrame.
2011-07-10 21:43:07 +00:00
# (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.
2019-03-24 07:53:19 +00:00
win_id = int(output_display.vlc_widget.winId())
if is_win():
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.set_hwnd(win_id)
elif is_macosx():
2015-11-07 00:49:40 +00:00
# We have to use 'set_nsobject' since Qt5 on OSX uses Cocoa
# framework and not the old Carbon.
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.set_nsobject(win_id)
else:
2015-04-28 14:01:09 +00:00
# for Linux/*BSD using the X Server
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.set_xwindow(win_id)
2013-03-06 22:54:16 +00:00
self.has_own_widget = True
2011-07-10 21:43:07 +00:00
def check_available(self):
"""
Return the availability of VLC
"""
return get_vlc() is not None
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def load(self, output_display, file):
"""
Load a video into VLC
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2018-11-01 20:51:42 +00:00
:param file: file to be played
2016-01-01 20:39:26 +00:00
:return:
"""
vlc = get_vlc()
2013-08-31 18:17:38 +00:00
log.debug('load vid in Vlc Controller')
2019-03-24 07:53:19 +00:00
controller = output_display
2011-07-10 21:43:07 +00:00
volume = controller.media_info.volume
2018-11-01 20:51:42 +00:00
path = os.path.normcase(file)
2011-07-10 21:43:07 +00:00
# create the media
if controller.media_info.media_type == MediaType.CD:
if is_win():
path = '/' + path
2019-03-24 07:53:19 +00:00
output_display.vlc_media = output_display.vlc_instance.media_new_location('cdda://' + path)
output_display.vlc_media_player.set_media(output_display.vlc_media)
output_display.vlc_media_player.play()
# Wait for media to start playing. In this case VLC actually returns an error.
2019-03-24 07:53:19 +00:00
self.media_state_wait(output_display, vlc.State.Playing)
# If subitems exists, this is a CD
2019-03-24 07:53:19 +00:00
audio_cd_tracks = output_display.vlc_media.subitems()
if not audio_cd_tracks or audio_cd_tracks.count() < 1:
return False
2019-03-24 07:53:19 +00:00
output_display.vlc_media = audio_cd_tracks.item_at_index(controller.media_info.title_track)
2019-01-20 17:58:41 +00:00
elif controller.media_info.media_type == MediaType.Stream:
2019-04-08 17:19:18 +00:00
output_display.vlc_media = output_display.vlc_instance.media_new_location('ZZZZZZ')
else:
2019-03-24 07:53:19 +00:00
output_display.vlc_media = output_display.vlc_instance.media_new_path(path)
2011-07-10 21:43:07 +00:00
# put the media in the media player
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.set_media(output_display.vlc_media)
2011-07-10 21:43:07 +00:00
# parse the metadata of the file
2019-03-24 07:53:19 +00:00
output_display.vlc_media.parse()
self.volume(output_display, volume)
2011-07-10 21:43:07 +00:00
return True
2019-03-24 07:53:19 +00:00
def media_state_wait(self, output_display, media_state):
2011-07-10 21:43:07 +00:00
"""
Wait for the video to change its state
2011-12-02 22:00:28 +00:00
Wait no longer than 60 seconds. (loading an iso file needs a long time)
2016-01-01 20:39:26 +00:00
:param media_state: The state of the playing media
:param display: The display where the media is
:return:
2011-07-10 21:43:07 +00:00
"""
vlc = get_vlc()
2011-07-10 21:43:07 +00:00
start = datetime.now()
2019-03-24 07:53:19 +00:00
while media_state != output_display.vlc_media.get_state():
if output_display.vlc_media.get_state() == vlc.State.Error:
2011-07-10 21:43:07 +00:00
return False
2013-02-03 19:23:12 +00:00
self.application.process_events()
2011-12-02 22:00:28 +00:00
if (datetime.now() - start).seconds > 60:
2011-07-10 21:43:07 +00:00
return False
return True
2019-03-24 07:53:19 +00:00
def resize(self, output_display):
"""
Resize the player
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:return:
"""
2019-03-25 21:45:19 +00:00
output_display.vlc_widget.resize(output_display.size())
2012-01-19 19:13:19 +00:00
2019-03-24 07:53:19 +00:00
def play(self, controller, output_display):
"""
Play the current item
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:return:
"""
vlc = get_vlc()
2011-07-25 20:56:39 +00:00
start_time = 0
log.debug('vlc play')
2019-03-24 07:53:19 +00:00
if output_display.is_display:
if self.get_live_state() != MediaState.Paused and output_display.media_info.start_time > 0:
start_time = output_display.media_info.start_time
2016-02-26 16:00:04 +00:00
else:
2019-03-24 07:53:19 +00:00
if self.get_preview_state() != MediaState.Paused and output_display.media_info.start_time > 0:
start_time = output_display.media_info.start_time
threading.Thread(target=output_display.vlc_media_player.play).start()
if not self.media_state_wait(output_display, vlc.State.Playing):
2011-08-29 19:55:58 +00:00
return False
2019-03-24 07:53:19 +00:00
if output_display.is_display:
if self.get_live_state() != MediaState.Paused and output_display.media_info.start_time > 0:
2016-02-26 16:00:04 +00:00
log.debug('vlc play, start time set')
2019-03-24 07:53:19 +00:00
start_time = output_display.media_info.start_time
2016-02-26 16:00:04 +00:00
else:
2019-03-24 07:53:19 +00:00
if self.get_preview_state() != MediaState.Paused and output_display.media_info.start_time > 0:
2016-02-26 16:00:04 +00:00
log.debug('vlc play, start time set')
2019-03-24 07:53:19 +00:00
start_time = output_display.media_info.start_time
log.debug('mediatype: ' + str(output_display.media_info.media_type))
# Set tracks for the optical device
2019-03-24 07:53:19 +00:00
if output_display.media_info.media_type == MediaType.DVD and \
2017-12-04 20:49:59 +00:00
self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused:
log.debug('vlc play, playing started')
2019-03-24 07:53:19 +00:00
if output_display.media_info.title_track > 0:
log.debug('vlc play, title_track set: ' + str(output_display.media_info.title_track))
output_display.vlc_media_player.set_title(output_display.media_info.title_track)
output_display.vlc_media_player.play()
if not self.media_state_wait(output_display, vlc.State.Playing):
return False
2019-03-24 07:53:19 +00:00
if output_display.media_info.audio_track > 0:
output_display.vlc_media_player.audio_set_track(output_display.media_info.audio_track)
log.debug('vlc play, audio_track set: ' + str(output_display.media_info.audio_track))
if output_display.media_info.subtitle_track > 0:
output_display.vlc_media_player.video_set_spu(output_display.media_info.subtitle_track)
log.debug('vlc play, subtitle_track set: ' + str(output_display.media_info.subtitle_track))
if output_display.media_info.start_time > 0:
log.debug('vlc play, starttime set: ' + str(output_display.media_info.start_time))
start_time = output_display.media_info.start_time
output_display.media_info.length = output_display.media_info.end_time - output_display.media_info.start_time
self.volume(output_display, output_display.media_info.volume)
if start_time > 0 and output_display.vlc_media_player.is_seekable():
output_display.vlc_media_player.set_time(int(start_time))
controller.seek_slider.setMaximum(output_display.media_info.length)
self.set_state(MediaState.Playing, output_display)
output_display.vlc_widget.raise_()
2012-06-09 15:46:01 +00:00
return True
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def pause(self, output_display):
"""
Pause the current item
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:return:
"""
vlc = get_vlc()
2019-03-24 07:53:19 +00:00
if output_display.vlc_media.get_state() != vlc.State.Playing:
2011-07-18 21:25:10 +00:00
return
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.pause()
if self.media_state_wait(output_display, vlc.State.Paused):
self.set_state(MediaState.Paused, output_display)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def stop(self, output_display):
"""
Stop the current item
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:return:
"""
2019-03-24 07:53:19 +00:00
threading.Thread(target=output_display.vlc_media_player.stop).start()
self.set_state(MediaState.Stopped, output_display)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def volume(self, output_display, vol):
"""
Set the volume
2016-01-01 20:39:26 +00:00
:param vol: The volume to be sets
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:return:
"""
2019-03-24 07:53:19 +00:00
if output_display.has_audio:
output_display.vlc_media_player.audio_set_volume(vol)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def seek(self, output_display, seek_value):
"""
Go to a particular position
2016-01-01 20:39:26 +00:00
:param seek_value: The position of where a seek goes to
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
"""
2019-03-24 07:53:19 +00:00
if output_display.controller.media_info.media_type == MediaType.CD \
or output_display.controller.media_info.media_type == MediaType.DVD:
seek_value += int(output_display.controller.media_info.start_time)
if output_display.vlc_media_player.is_seekable():
output_display.vlc_media_player.set_time(seek_value)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def reset(self, output_display):
"""
Reset the player
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
"""
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.stop()
output_display.vlc_widget.setVisible(False)
self.set_state(MediaState.Off, output_display)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def set_visible(self, output_display, status):
"""
Set the visibility
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param output_display: The display where the media is
2016-01-01 20:39:26 +00:00
:param status: The visibility status
"""
2013-03-06 22:54:16 +00:00
if self.has_own_widget:
2019-03-24 07:53:19 +00:00
output_display.vlc_widget.setVisible(status)
2011-07-10 21:43:07 +00:00
2019-03-24 07:53:19 +00:00
def update_ui(self, controller, output_display):
"""
Update the UI
2016-01-01 20:39:26 +00:00
2019-03-24 07:53:19 +00:00
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
"""
vlc = get_vlc()
# Stop video if playback is finished.
2019-03-24 07:53:19 +00:00
if output_display.vlc_media.get_state() == vlc.State.Ended:
self.stop(output_display)
2011-07-25 20:56:39 +00:00
if controller.media_info.end_time > 0:
2019-03-24 07:53:19 +00:00
if output_display.vlc_media_player.get_time() > controller.media_info.end_time:
self.stop(output_display)
self.set_visible(output_display, False)
2013-03-23 07:28:24 +00:00
if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True)
2019-03-24 07:53:19 +00:00
if controller.media_info.media_type == MediaType.CD \
or controller.media_info.media_type == MediaType.DVD:
controller.seek_slider.setSliderPosition(
2019-03-24 07:53:19 +00:00
output_display.vlc_media_player.get_time() - int(output_display.controller.media_info.start_time))
2014-07-01 20:06:55 +00:00
else:
2019-03-24 07:53:19 +00:00
controller.seek_slider.setSliderPosition(output_display.vlc_media_player.get_time())
2013-03-23 07:28:24 +00:00
controller.seek_slider.blockSignals(False)
2011-12-02 15:15:31 +00:00
2012-10-15 17:35:14 +00:00
def get_info(self):
"""
Return some information about this player
"""
2012-10-15 17:35:14 +00:00
return(translate('Media.player', 'VLC is an external player which '
2013-12-31 20:29:03 +00:00
'supports a number of different formats.') +
'<br/> <strong>' + translate('Media.player', 'Audio') +
'</strong><br/>' + str(AUDIO_EXT) + '<br/><strong>' +
translate('Media.player', 'Video') + '</strong><br/>' +
2014-03-20 19:10:31 +00:00
str(VIDEO_EXT) + '<br/>')