openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #27097
[Merge] lp:~marmyshev/openlp/video_list into lp:openlp
Dmitriy Marmyshev has proposed merging lp:~marmyshev/openlp/video_list into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~marmyshev/openlp/video_list/+merge/262944
just to review all changes
able to play list of media files, optionally play list or item in loop, store volume level in service.
list support for phonon, action at the end in service manager
--
Your team OpenLP Core is requested to review the proposed merge of lp:~marmyshev/openlp/video_list into lp:openlp.
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py 2015-02-21 13:08:56 +0000
+++ openlp/core/common/settings.py 2015-06-25 08:27:29 +0000
@@ -243,6 +243,9 @@
'shortcuts/playbackStop': [],
'shortcuts/playSlidesLoop': [],
'shortcuts/playSlidesOnce': [],
+ 'shortcuts/playSlidesNext': [],
+ 'shortcuts/actionAtTheEndLoop': [],
+ 'shortcuts/actionAtTheEndNext': [],
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
'shortcuts/previousItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
=== modified file 'openlp/core/common/uistrings.py'
--- openlp/core/common/uistrings.py 2015-04-21 21:49:22 +0000
+++ openlp/core/common/uistrings.py 2015-06-25 08:27:29 +0000
@@ -114,6 +114,9 @@
self.OpenService = translate('OpenLP.Ui', 'Open service.')
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
+ self.PlaySlidesToEndAndNext = translate('OpenLP.Ui', 'Play Slides to end and Go Live Next item')
+ self.ActionAtTheEndLoop = translate('OpenLP.Ui', 'Play in Loop')
+ self.ActionAtTheEndNext = translate('OpenLP.Ui', 'Play to end and Go Live Next item')
self.Preview = translate('OpenLP.Ui', 'Preview')
self.PreviewToolbar = translate('OpenLP.Ui', 'Preview Toolbar')
self.PrintService = translate('OpenLP.Ui', 'Print Service')
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2015-04-02 09:04:56 +0000
+++ openlp/core/lib/__init__.py 2015-06-25 08:27:29 +0000
@@ -322,7 +322,7 @@
from .plugin import PluginStatus, StringContent, Plugin
from .pluginmanager import PluginManager
from .settingstab import SettingsTab
-from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
+from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities, ActionAtTheEnd
from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css
from .toolbar import OpenLPToolbar
from .dockwidget import OpenLPDockWidget
=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py 2015-06-10 15:14:23 +0000
+++ openlp/core/lib/serviceitem.py 2015-06-25 08:27:29 +0000
@@ -118,6 +118,9 @@
``HasThumbnails``
The item has related thumbnails available
+ ``HasActionAtTheEnd``
+ The item has an action at the end playing
+
"""
CanPreview = 1
CanEdit = 2
@@ -140,7 +143,26 @@
HasDisplayTitle = 19
HasNotes = 20
HasThumbnails = 21
-
+ HasActionAtTheEnd = 22
+
+
+class ActionAtTheEnd(object):
+ """
+ Defines the type of actions at the end of playing item
+
+ ``Nothing``
+ The item will do nothing
+
+ ``Loop``
+ The item will loop
+
+ ``Next``
+ The item will play next item in service
+
+ """
+ Nothing = 'nothing'
+ Loop = 'loop'
+ Next = 'next'
class ServiceItem(RegistryProperties):
"""
@@ -186,6 +208,8 @@
self.start_time = 0
self.end_time = 0
self.media_length = 0
+ self.volume = 100
+ self.action_at_the_end = ActionAtTheEnd.Nothing
self.from_service = False
self.image_border = '#000000'
self.background_audio = []
@@ -334,6 +358,8 @@
file_location_hash, ntpath.basename(image))
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
'display_title': display_title, 'notes': notes})
+ if self.is_capable(ItemCapabilities.HasVariableStartTime) and len(self._raw_frames) > 1:
+ self.capabilities.remove(ItemCapabilities.HasVariableStartTime)
self._new_item()
def get_service_repr(self, lite_save):
@@ -361,6 +387,8 @@
'start_time': self.start_time,
'end_time': self.end_time,
'media_length': self.media_length,
+ 'volume': self.volume,
+ 'action_at_the_end': self.action_at_the_end,
'background_audio': self.background_audio,
'theme_overwritten': self.theme_overwritten,
'will_auto_start': self.will_auto_start,
@@ -409,6 +437,8 @@
self.start_time = header.get('start_time', 0)
self.end_time = header.get('end_time', 0)
self.media_length = header.get('media_length', 0)
+ self.volume = header.get('volume', 100)
+ self.action_at_the_end = header.get('action_at_the_end', ActionAtTheEnd.Nothing)
self.auto_play_slides_once = header.get('auto_play_slides_once', False)
self.auto_play_slides_loop = header.get('auto_play_slides_loop', False)
self.timed_slide_interval = header.get('timed_slide_interval', 0)
@@ -548,7 +578,7 @@
:param length: The length of the media item
"""
self.media_length = length
- if length > 0:
+ if length > 0 and len(self._raw_frames) == 1:
self.add_capability(ItemCapabilities.HasVariableStartTime)
def get_frames(self):
=== modified file 'openlp/core/ui/media/__init__.py'
--- openlp/core/ui/media/__init__.py 2015-01-18 13:39:21 +0000
+++ openlp/core/ui/media/__init__.py 2015-06-25 08:27:29 +0000
@@ -58,10 +58,11 @@
"""
This class hold the media related info
"""
- file_info = None
+ file_info = []
volume = 100
is_flash = False
is_background = False
+ playback_loop = False
length = 0
start_time = 0
end_time = 0
=== modified file 'openlp/core/ui/media/mediacontroller.py'
--- openlp/core/ui/media/mediacontroller.py 2015-05-28 20:38:43 +0000
+++ openlp/core/ui/media/mediacontroller.py 2015-06-25 08:27:29 +0000
@@ -29,7 +29,7 @@
from PyQt4 import QtCore, QtGui
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate
-from openlp.core.lib import OpenLPToolbar, ItemCapabilities
+from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ActionAtTheEnd
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
parse_optical_path
@@ -100,11 +100,19 @@
self.timer.setInterval(200)
# Signals
self.timer.timeout.connect(self.media_state)
+ # Timer for video current item in list
+ self.list_timer = QtCore.QTimer()
+ self.list_timer.setInterval(500)
+ # Signals
+ self.list_timer.timeout.connect(self.current_list_item)
Registry().register_function('playbackPlay', self.media_play_msg)
Registry().register_function('playbackPause', self.media_pause_msg)
Registry().register_function('playbackStop', self.media_stop_msg)
Registry().register_function('seek_slider', self.media_seek_msg)
Registry().register_function('volume_slider', self.media_volume_msg)
+ Registry().register_function('media_slide', self.media_slide)
+ Registry().register_function('media_next', self.media_next)
+ Registry().register_function('media_previous', self.media_previous)
Registry().register_function('media_hide', self.media_hide)
Registry().register_function('media_blank', self.media_blank)
Registry().register_function('media_unblank', self.media_unblank)
@@ -210,10 +218,43 @@
if self.current_media_players[source].state != MediaState.Paused:
display = self._define_display(self.display_controllers[source])
display.controller.seek_slider.setSliderPosition(0)
- display.controller.mediabar.actions['playbackPlay'].setVisible(True)
- display.controller.mediabar.actions['playbackPause'].setVisible(False)
+ if display.is_live and display.service_item.action_at_the_end == ActionAtTheEnd.Next:
+ display.service_manager.next_item()
+ if False and not self.current_media_players[source].can_play_list:
+ # FIXME: pay next file in current service item
+ return
+ else:
+ display.controller.mediabar.actions['playbackPlay'].setVisible(True)
+ display.controller.mediabar.actions['playbackPause'].setVisible(False)
self.timer.stop()
+ def current_list_item(self):
+ """
+ Check current list item, if changed update slidecontroller and media info
+ """
+ if not list(self.current_media_players.keys()):
+ self.list_timer.stop()
+ else:
+ any_active = False
+ for source in list(self.current_media_players.keys()):
+ if self.current_media_players[source].state == MediaState.Playing:
+ any_active = True
+ display = self._define_display(self.display_controllers[source])
+ if not self.current_media_players[source].can_play_list or len(display.service_item.get_frames()) == 1:
+ continue
+ index = self.current_media_players[source].get_current_index(display)
+ if display.current_list_index != index:
+ display.current_list_index = index
+ controller = display.controller
+ controller.seek_slider.setMaximum(controller.media_info.length * 1000)
+ if display.is_live:
+ prefix = 'live'
+ else:
+ prefix = 'preview'
+ Registry().execute('slidecontroller_%s_change' % prefix, index)
+ if not any_active:
+ self.list_timer.stop()
+
def get_media_display_css(self):
"""
Add css style sheets to htmlbuilder
@@ -359,9 +400,12 @@
# stop running videos
self.media_reset(controller)
controller.media_info = MediaInfo()
- controller.media_info.volume = controller.volume_slider.value()
+ controller.media_info.volume = service_item.volume
controller.media_info.is_background = video_behind_text
- controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
+ controller.media_info.playback_loop = service_item.action_at_the_end == ActionAtTheEnd.Loop
+ controller.media_info.file_info = []
+ for row in range(len(service_item.get_frames())):
+ controller.media_info.file_info.append(QtCore.QFileInfo(service_item.get_frame_path(row)))
display = self._define_display(controller)
if controller.is_live:
# if this is an optical device use special handling
@@ -395,8 +439,8 @@
is_valid = self._check_file_type(controller, display, service_item)
if not is_valid:
# Media could not be loaded correctly
- critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
- translate('MediaPlugin.MediaItem', 'Unsupported File'))
+ critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File(s)'),
+ translate('MediaPlugin.MediaItem', 'Unsupported File(s)'))
return False
log.debug('video mediatype: ' + str(controller.media_info.media_type))
# dont care about actual theme, set a black background
@@ -415,8 +459,8 @@
autoplay = True
if autoplay:
if not self.media_play(controller):
- critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
- translate('MediaPlugin.MediaItem', 'Unsupported File'))
+ critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File(s)'),
+ translate('MediaPlugin.MediaItem', 'Unsupported File(s)'))
return False
self.set_controls_visible(controller, True)
log.debug('use %s controller' % self.current_media_players[controller.controller_type])
@@ -434,16 +478,18 @@
self.media_reset(controller)
controller.media_info = MediaInfo()
controller.media_info.volume = 0
- controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
+ controller.media_info.file_info = []
+ for row in range(len(service_item.get_frames())):
+ controller.media_info.file_info.append(QtCore.QFileInfo(service_item.get_frame_path(row)))
display = controller.preview_display
if not self._check_file_type(controller, display, service_item):
# Media could not be loaded correctly
- critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
- translate('MediaPlugin.MediaItem', 'Unsupported File'))
+ critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File(s)'),
+ translate('MediaPlugin.MediaItem', 'Unsupported File(s)'))
return False
if not self.media_play(controller):
- critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
- translate('MediaPlugin.MediaItem', 'Unsupported File'))
+ critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File(s)'),
+ translate('MediaPlugin.MediaItem', 'Unsupported File(s)'))
return False
service_item.set_media_length(controller.media_info.length)
self.media_stop(controller)
@@ -471,7 +517,8 @@
self.media_reset(controller)
# Setup media info
controller.media_info = MediaInfo()
- controller.media_info.file_info = QtCore.QFileInfo(filename)
+ controller.media_info.file_info = []
+ controller.media_info.file_info.append(QtCore.QFileInfo(filename))
if audio_track == -1 and subtitle_track == -1:
controller.media_info.media_type = MediaType.CD
else:
@@ -520,36 +567,47 @@
# If no player, we can't play
if not used_players:
return False
- if controller.media_info.file_info.isFile():
- suffix = '*.%s' % controller.media_info.file_info.suffix().lower()
- for title in used_players:
- if not title:
- continue
- player = self.media_players[title]
- if suffix in player.video_extensions_list:
- if not controller.media_info.is_background or controller.media_info.is_background and \
- player.can_background:
+ list_players_only = len(controller.media_info.file_info) > 1
+ list_player_title = ''
+ for file_info in controller.media_info.file_info:
+ valid_player_found = False
+ if file_info.isFile():
+ suffix = '*.%s' % file_info.suffix().lower()
+ for title in used_players:
+ # skip empty titles, or search in founded list player only
+ if not title or list_players_only and list_player_title and list_player_title != title:
+ continue
+ player = self.media_players[title]
+ if list_players_only and not player.can_play_list:
+ continue
+ if suffix in player.video_extensions_list:
+ if not controller.media_info.is_background or controller.media_info.is_background and \
+ player.can_background:
+ self.resize(display, player)
+ if player.load(display):
+ self.current_media_players[controller.controller_type] = player
+ controller.media_info.media_type = MediaType.Video
+ valid_player_found = True
+ list_player_title = title
+ if suffix in player.audio_extensions_list:
+ if player.load(display):
+ self.current_media_players[controller.controller_type] = player
+ controller.media_info.media_type = MediaType.Audio
+ valid_player_found = True
+ list_player_title = title
+ else:
+ for title in used_players:
+ player = self.media_players[title]
+ if player.can_folder:
self.resize(display, player)
if player.load(display):
self.current_media_players[controller.controller_type] = player
controller.media_info.media_type = MediaType.Video
- return True
- if suffix in player.audio_extensions_list:
- if player.load(display):
- self.current_media_players[controller.controller_type] = player
- controller.media_info.media_type = MediaType.Audio
- return True
- else:
- for title in used_players:
- player = self.media_players[title]
- if player.can_folder:
- self.resize(display, player)
- if player.load(display):
- self.current_media_players[controller.controller_type] = player
- controller.media_info.media_type = MediaType.Video
- return True
- # no valid player found
- return False
+ valid_player_found = True
+ if not valid_player_found:
+ # no valid player found
+ return False
+ return True
def media_play_msg(self, msg, status=True):
"""
@@ -598,6 +656,8 @@
# Start Timer for ui updates
if not self.timer.isActive():
self.timer.start()
+ if not self.list_timer.isActive():
+ self.list_timer.start()
controller.seek_slider.blockSignals(False)
controller.volume_slider.blockSignals(False)
return True
@@ -650,6 +710,116 @@
controller.mediabar.actions['playbackStop'].setDisabled(True)
controller.mediabar.actions['playbackPause'].setVisible(False)
+ def media_slide(self, msg):
+ """
+ Blank the related video Widget
+
+ :param msg: First element is the boolean for Live indication
+ Second element is the hide mode
+ """
+ log.debug('media_play')
+ service_item = msg[0]
+ is_live = msg[1]
+ index = msg[2]
+ status = True
+ # Should not react on slide select if only one slide
+ if len(service_item.get_frames()) == 1:
+ return
+ if is_live:
+ controller = self.live_controller
+ else:
+ controller = self.preview_controller
+ controller.seek_slider.blockSignals(True)
+ controller.volume_slider.blockSignals(True)
+ display = self._define_display(controller)
+ if not self.current_media_players[controller.controller_type].play(display, index):
+ controller.seek_slider.blockSignals(False)
+ controller.volume_slider.blockSignals(False)
+ return False
+ if controller.media_info.is_background:
+ self.media_volume(controller, 0)
+ else:
+ self.media_volume(controller, controller.media_info.volume)
+ if status:
+ if not controller.media_info.is_background:
+ display.frame.evaluateJavaScript('show_blank("desktop");')
+ self.current_media_players[controller.controller_type].set_visible(display, True)
+ # Flash needs to be played and will not AutoPlay
+ if controller.media_info.is_flash:
+ controller.mediabar.actions['playbackPlay'].setVisible(True)
+ controller.mediabar.actions['playbackPause'].setVisible(False)
+ else:
+ controller.mediabar.actions['playbackPlay'].setVisible(False)
+ controller.mediabar.actions['playbackPause'].setVisible(True)
+ controller.mediabar.actions['playbackStop'].setDisabled(False)
+ if controller.is_live:
+ if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background:
+ controller.hide_menu.defaultAction().trigger()
+ # Start Timer for ui updates
+ if not self.timer.isActive():
+ self.timer.start()
+ controller.seek_slider.blockSignals(False)
+ controller.volume_slider.blockSignals(False)
+ return True
+
+ def media_next(self, msg):
+ """
+ Blank the related video Widget
+
+ :param msg: First element is the boolean for Live indication
+ Second element is the hide mode
+ """
+ service_item = msg[0]
+ is_live = msg[1]
+ # Should not react on slide select if only one slide
+ if len(service_item.get_frames()) == 1:
+ return
+ if is_live:
+ controller = self.live_controller
+ else:
+ controller = self.preview_controller
+ controller.seek_slider.blockSignals(True)
+ controller.volume_slider.blockSignals(True)
+ display = self._define_display(controller)
+ if not self.current_media_players[controller.controller_type].next(display):
+ controller.seek_slider.blockSignals(False)
+ controller.volume_slider.blockSignals(False)
+
+ def media_previous(self, msg):
+ """
+ Blank the related video Widget
+
+ :param msg: First element is the boolean for Live indication
+ Second element is the hide mode
+ """
+ service_item = msg[0]
+ is_live = msg[1]
+ # Should not react on slide select if only one slide
+ if len(service_item.get_frames()) == 1:
+ return
+ if is_live:
+ controller = self.live_controller
+ else:
+ controller = self.preview_controller
+ controller.seek_slider.blockSignals(True)
+ controller.volume_slider.blockSignals(True)
+ display = self._define_display(controller)
+ if not self.current_media_players[controller.controller_type].previous(display):
+ controller.seek_slider.blockSignals(False)
+ controller.volume_slider.blockSignals(False)
+
+ def update_action_at_the_end(self, action_at_the_end):
+ """
+ Updates action at the end on live controller
+
+ :param action_at_the_end: Action at the end of Service item
+ """
+ controller = self.live_controller
+ display = self._define_display(controller)
+ controller.media_info.playback_loop = action_at_the_end == ActionAtTheEnd.Loop
+ player = self.current_media_players[controller.controller_type]
+ player.set_playback_loop(display, controller.media_info.playback_loop)
+
def media_volume_msg(self, msg):
"""
Changes the volume of a running video
@@ -760,12 +930,15 @@
# Start Timer for ui updates
if not self.timer.isActive():
self.timer.start()
+ if not self.list_timer.isActive():
+ self.list_timer.start()
def finalise(self):
"""
Reset all the media controllers when OpenLP shuts down
"""
self.timer.stop()
+ self.list_timer.stop()
for controller in self.display_controllers:
self.media_reset(self.display_controllers[controller])
=== modified file 'openlp/core/ui/media/mediaplayer.py'
--- openlp/core/ui/media/mediaplayer.py 2015-01-18 13:39:21 +0000
+++ openlp/core/ui/media/mediaplayer.py 2015-06-25 08:27:29 +0000
@@ -40,6 +40,7 @@
self.available = self.check_available()
self.is_active = False
self.can_background = False
+ self.can_play_list = False
self.can_folder = False
self.state = MediaState.Off
self.has_own_widget = False
@@ -71,7 +72,7 @@
"""
pass
- def play(self, display):
+ def play(self, display, index=0):
"""
Starts playing of current Media File
"""
@@ -89,6 +90,30 @@
"""
pass
+ def next(self, display):
+ """
+ Starts playing next Media File
+ """
+ pass
+
+ def previous(self, display):
+ """
+ Starts playing previous Media File
+ """
+ pass
+
+ def set_playback_loop(self, display, loop=True):
+ """
+ Sets playback mode of list of Media Player
+ """
+ pass
+
+ def get_current_index(self, display):
+ """
+ Gets index of playing media item of playlist
+ """
+ pass
+
def volume(self, display, vol):
"""
Change volume of current Media File
=== modified file 'openlp/core/ui/media/phononplayer.py'
--- openlp/core/ui/media/phononplayer.py 2015-01-18 13:39:21 +0000
+++ openlp/core/ui/media/phononplayer.py 2015-06-25 08:27:29 +0000
@@ -69,6 +69,7 @@
self.display_name = '&Phonon'
self.parent = parent
self.additional_extensions = ADDITIONAL_EXT
+ self.can_play_list = True
mimetypes.init()
for mime_type in Phonon.BackendCapabilities.availableMimeTypes():
mime_type = str(mime_type)
@@ -103,6 +104,10 @@
"""
Set up the player widgets
"""
+ display.phonon_play_list = []
+ display.phonon_current_index = -1
+ display.current_list_index = -1
+ display.phonon_repeat = False
display.phonon_widget = Phonon.VideoWidget(display)
display.phonon_widget.resize(display.size())
display.media_object = Phonon.MediaObject(display)
@@ -110,10 +115,35 @@
if display.has_audio:
display.audio = Phonon.AudioOutput(Phonon.VideoCategory, display.media_object)
Phonon.createPath(display.media_object, display.audio)
+ if display.is_live:
+ display.media_object.aboutToFinish.connect(self.on_about_to_finish)
+ display.media_object.finished.connect(self.on_finished)
display.phonon_widget.raise_()
display.phonon_widget.hide()
self.has_own_widget = True
+ def on_about_to_finish(self):
+ """
+ Just before the phonon player finishes the current track, queue the next
+ item in the playlist, if there is one.
+ """
+ display = self.live_controller.display
+ display.phonon_current_index += 1
+ if len(display.phonon_play_list) > display.phonon_current_index:
+ display.media_object.enqueue(display.phonon_play_list[display.phonon_current_index])
+
+ def on_finished(self):
+ """
+ When the audio track finishes.
+ """
+ display = self.live_controller.display
+ if display.phonon_repeat:
+ self.log_debug('Repeat is enabled... here we go again!')
+ display.media_object.clearQueue()
+ display.media_object.clear()
+ display.phonon_current_index = -1
+ self.play(display)
+
def check_available(self):
"""
Check if the player is available
@@ -131,8 +161,13 @@
log.debug('load vid in Phonon Controller')
controller = display.controller
volume = controller.media_info.volume
- path = controller.media_info.file_info.absoluteFilePath()
- display.media_object.setCurrentSource(Phonon.MediaSource(path))
+ display.phonon_play_list.clear()
+ for file_info in controller.media_info.file_info:
+ path = file_info.absoluteFilePath()
+ display.phonon_play_list.append(path)
+ display.current_list_index = 0
+ display.phonon_current_index = 0
+ display.media_object.setCurrentSource(Phonon.MediaSource(display.phonon_play_list[0]))
if not self.media_state_wait(display, Phonon.StoppedState):
return False
self.volume(display, volume)
@@ -160,7 +195,7 @@
"""
display.phonon_widget.resize(display.size())
- def play(self, display):
+ def play(self, display, index=0):
"""
Play the current media item
"""
@@ -168,6 +203,10 @@
start_time = 0
if display.media_object.state() != Phonon.PausedState and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
+ if display.phonon_current_index != index:
+ display.phonon_current_index = index
+ path = display.phonon_play_list[index]
+ display.media_object.setCurrentSource(Phonon.MediaSource(path))
display.media_object.play()
if not self.media_state_wait(display, Phonon.PlayingState):
return False
@@ -196,6 +235,42 @@
self.set_visible(display, False)
self.state = MediaState.Stopped
+ def next(self, display):
+ """
+ Starts playing next Media File
+ """
+ phonon_current_index = display.phonon_current_index
+ phonon_current_index += 1
+ if len(display.phonon_play_list) <= phonon_current_index and display.phonon_repeat:
+ phonon_current_index = 0
+ if len(display.phonon_play_list) > phonon_current_index:
+ self.play(display, phonon_current_index)
+
+ def previous(self, display):
+ """
+ Starts playing previous Media File
+ """
+ phonon_current_index = display.phonon_current_index
+ phonon_current_index -= 1
+ if phonon_current_index < 0 and display.phonon_repeat:
+ phonon_current_index = len(display.phonon_play_list) - 1
+ if len(display.phonon_play_list) > phonon_current_index:
+ self.play(display, phonon_current_index)
+
+ def set_playback_loop(self, display, loop=True):
+ """
+ Sets playback mode of list of Media Player
+ """
+ display.phonon_repeat = loop
+
+ def get_current_index(self, display):
+ """
+ Gets index of playing media item of playlist
+ """
+ controller = display.controller
+ controller.media_info.length = int(display.media_object.totalTime() / 1000)
+ return display.phonon_current_index
+
def volume(self, display, vol):
"""
Set the volume
=== modified file 'openlp/core/ui/media/vlcplayer.py'
--- openlp/core/ui/media/vlcplayer.py 2015-04-28 14:01:09 +0000
+++ openlp/core/ui/media/vlcplayer.py 2015-06-25 08:27:29 +0000
@@ -124,6 +124,7 @@
self.display_name = '&VLC'
self.parent = parent
self.can_folder = True
+ self.can_play_list = True
self.audio_extensions_list = AUDIO_EXT
self.video_extensions_list = VIDEO_EXT
@@ -142,7 +143,10 @@
command_line_options += ' --mouse-hide-timeout=0'
display.vlc_instance = vlc.Instance(command_line_options)
# creating an empty vlc media player
+ display.vlc_media_list_player = display.vlc_instance.media_list_player_new()
+ display.current_list_index = -1
display.vlc_media_player = display.vlc_instance.media_player_new()
+ display.vlc_media_list_player.set_media_player(display.vlc_media_player)
display.vlc_widget.resize(display.size())
display.vlc_widget.raise_()
display.vlc_widget.hide()
@@ -177,10 +181,14 @@
log.debug('load vid in Vlc Controller')
controller = display.controller
volume = controller.media_info.volume
- file_path = str(controller.media_info.file_info.absoluteFilePath())
- path = os.path.normcase(file_path)
+ if hasattr(display, 'vlc_media_list') and display.vlc_media_list:
+ display.vlc_media_list.release()
+ display.vlc_media_list = display.vlc_instance.media_list_new()
+ display.vlc_media_list_player.set_media_list(display.vlc_media_list)
# create the media
if controller.media_info.media_type == MediaType.CD:
+ file_path = str(controller.media_info.file_info[0].absoluteFilePath())
+ path = os.path.normcase(file_path)
if is_win():
path = '/' + path
display.vlc_media = display.vlc_instance.media_new_location('cdda://' + path)
@@ -193,10 +201,24 @@
if not audio_cd_tracks or audio_cd_tracks.count() < 1:
return False
display.vlc_media = audio_cd_tracks.item_at_index(controller.media_info.title_track)
+ display.vlc_media_list.add_media(vlc_media)
else:
- display.vlc_media = display.vlc_instance.media_new_path(path)
+ for file_info in controller.media_info.file_info:
+ file_path = str(file_info.absoluteFilePath())
+ path = os.path.normcase(file_path)
+ vlc_media = display.vlc_instance.media_new_path(path)
+ display.vlc_media_list.add_media(vlc_media)
# put the media in the media player
- display.vlc_media_player.set_media(display.vlc_media)
+ display.vlc_media_list.lock()
+ display.vlc_media = display.vlc_media_list.item_at_index(0)
+ display.current_list_index = 0
+ display.vlc_media_list.unlock()
+ display.vlc_media_list.set_media(display.vlc_media)
+ # set type of playing: default, loop or repeat
+ if controller.media_info.playback_loop:
+ display.vlc_media_list_player.set_playback_mode(vlc.PlaybackMode.loop)
+ else:
+ display.vlc_media_list_player.set_playback_mode(vlc.PlaybackMode.default)
# parse the metadata of the file
display.vlc_media.parse()
self.volume(display, volume)
@@ -205,7 +227,7 @@
# and once to just get media length.
#
# Media plugin depends on knowing media length before playback.
- controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000)
+ controller.media_info.length = int(display.vlc_media_list.media().get_duration() / 1000)
return True
def media_state_wait(self, display, media_state):
@@ -229,7 +251,7 @@
"""
display.vlc_widget.resize(display.size())
- def play(self, display):
+ def play(self, display, index=0):
"""
Play the current item
"""
@@ -239,7 +261,7 @@
log.debug('vlc play')
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
- threading.Thread(target=display.vlc_media_player.play).start()
+ threading.Thread(target=display.vlc_media_list_player.play_item_at_index, args=[index]).start()
if not self.media_state_wait(display, vlc.State.Playing):
return False
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
@@ -266,7 +288,8 @@
start_time = controller.media_info.start_time
controller.media_info.length = controller.media_info.end_time - controller.media_info.start_time
else:
- controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000)
+ display.vlc_media = display.vlc_media_player.get_media()
+ controller.media_info.length = int(display.vlc_media.get_duration() / 1000)
self.volume(display, controller.media_info.volume)
if start_time > 0 and display.vlc_media_player.is_seekable():
display.vlc_media_player.set_time(int(start_time * 1000))
@@ -282,7 +305,7 @@
vlc = get_vlc()
if display.vlc_media.get_state() != vlc.State.Playing:
return
- display.vlc_media_player.pause()
+ display.vlc_media_list_player.pause()
if self.media_state_wait(display, vlc.State.Paused):
self.state = MediaState.Paused
@@ -290,9 +313,44 @@
"""
Stop the current item
"""
- threading.Thread(target=display.vlc_media_player.stop).start()
+ threading.Thread(target=display.vlc_media_list_player.stop).start()
self.state = MediaState.Stopped
+ def next(self, display):
+ """
+ Starts playing next Media File
+ """
+ threading.Thread(target=display.vlc_media_list_player.next).start()
+ self.state = MediaState.Playing
+
+ def previous(self, display):
+ """
+ Starts playing previous Media File
+ """
+ threading.Thread(target=display.vlc_media_list_player.previous).start()
+ self.state = MediaState.Playing
+
+ def set_playback_loop(self, display, loop=True):
+ """
+ Sets playback mode of list of Media Player
+ """
+ vlc = get_vlc()
+ log.debug('set playback mode in Vlc')
+ if loop:
+ display.vlc_media_list_player.set_playback_mode(vlc.PlaybackMode.loop)
+ else:
+ display.vlc_media_list_player.set_playback_mode(vlc.PlaybackMode.default)
+
+ def get_current_index(self, display):
+ """
+ Gets index of playing media item of playlist
+ """
+ log.debug('get index of playing media in Vlc')
+ display.vlc_media = display.vlc_media_player.get_media()
+ controller = display.controller
+ controller.media_info.length = int(display.vlc_media.get_duration() / 1000)
+ return display.vlc_media_list.index_of_item(display.vlc_media)
+
def volume(self, display, vol):
"""
Set the volume
@@ -314,7 +372,7 @@
"""
Reset the player
"""
- display.vlc_media_player.stop()
+ display.vlc_media_list_player.stop()
display.vlc_widget.setVisible(False)
self.state = MediaState.Off
@@ -345,6 +403,7 @@
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time() -
int(display.controller.media_info.start_time * 1000))
else:
+ display.vlc_media = display.vlc_media_player.get_media()
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time())
controller.seek_slider.blockSignals(False)
=== modified file 'openlp/core/ui/media/webkitplayer.py'
--- openlp/core/ui/media/webkitplayer.py 2015-03-15 22:34:12 +0000
+++ openlp/core/ui/media/webkitplayer.py 2015-06-25 08:27:29 +0000
@@ -243,13 +243,13 @@
vol = float(volume) / float(100)
else:
vol = 0
- path = controller.media_info.file_info.absoluteFilePath()
+ path = controller.media_info.file_info[0].absoluteFilePath()
if controller.media_info.is_background:
loop = 'true'
else:
loop = 'false'
display.web_view.setVisible(True)
- if controller.media_info.file_info.suffix() == 'swf':
+ if controller.media_info.file_info[0].suffix() == 'swf':
controller.media_info.is_flash = True
js = 'show_flash("load","%s");' % (path.replace('\\', '\\\\'))
else:
=== modified file 'openlp/core/ui/serviceitemeditform.py'
--- openlp/core/ui/serviceitemeditform.py 2015-01-18 13:39:21 +0000
+++ openlp/core/ui/serviceitemeditform.py 2015-06-25 08:27:29 +0000
@@ -47,7 +47,7 @@
"""
self.item = item
self.item_list = []
- if self.item.is_image():
+ if self.item.is_image() or self.item.is_command():
self.data = True
self.item_list.extend(self.item._raw_frames)
self.load_data()
@@ -62,6 +62,9 @@
if self.item.is_image():
for item in self.item_list:
self.item.add_from_image(item['path'], item['title'])
+ elif self.item.is_command():
+ for item in self.item_list:
+ self.item.add_from_command(item['path'], item['title'], ':/media/slidecontroller_multimedia.png')
self.item.render()
return self.item
=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py 2015-03-09 22:10:21 +0000
+++ openlp/core/ui/servicemanager.py 2015-06-25 08:27:29 +0000
@@ -34,7 +34,7 @@
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
RegistryMixin, check_directory_exists, UiStrings, translate
-from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, build_icon
+from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, ActionAtTheEnd, PluginStatus, build_icon
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
from openlp.core.ui.printserviceform import PrintServiceForm
@@ -264,11 +264,58 @@
'&Once'),
checked=False, triggers=self.toggle_auto_play_slides_once)
auto_play_slides_group.addAction(self.auto_play_slides_once)
+ self.auto_play_slides_next = create_widget_action(self.auto_play_slides_menu,
+ text=translate('OpenLP.ServiceManager', 'Auto play slides '
+ 'once and Go Live '
+ 'Next item'),
+ checked=False, triggers=self.toggle_auto_play_slides_next)
+ auto_play_slides_group.addAction(self.auto_play_slides_next)
self.auto_play_slides_menu.addSeparator()
self.timed_slide_interval = create_widget_action(self.auto_play_slides_menu,
text=translate('OpenLP.ServiceManager', '&Delay between '
'slides'),
triggers=self.on_timed_slide_interval)
+ # Add Playback mode menu
+ self.playback_mode_menu = QtGui.QMenu(translate('OpenLP.ServiceManager', '&Playback mode'))
+ self.menu.addMenu(self.playback_mode_menu)
+ playback_mode_group = QtGui.QActionGroup(self.playback_mode_menu)
+ playback_mode_group.setExclusive(True)
+ self.playback_mode_default = create_widget_action(playback_mode_group,
+ text=translate('OpenLP.ServiceManager', 'Play &Ones'),
+ checked=True)
+ self.playback_mode_menu.addAction(self.playback_mode_default)
+ self.playback_mode_default.setData(ActionAtTheEnd.Nothing)
+ self.playback_mode_loop = create_widget_action(playback_mode_group,
+ text=translate('OpenLP.ServiceManager', 'Play &Loop'),
+ checked=False)
+ self.playback_mode_menu.addAction(self.playback_mode_loop)
+ self.playback_mode_loop.setData(ActionAtTheEnd.Loop)
+ self.playback_mode_next = create_widget_action(playback_mode_group,
+ text=translate('OpenLP.ServiceManager', 'Play ones and Go '
+ 'Live &Next item'),
+ checked=False)
+ self.playback_mode_menu.addAction(self.playback_mode_next)
+ self.playback_mode_next.setData(ActionAtTheEnd.Next)
+ playback_mode_group.triggered.connect(self.on_playback_mode_select)
+ # Add Volume menu
+ self.volume_menu = QtGui.QMenu(translate('OpenLP.ServiceManager', '&Volume'))
+ self.menu.addMenu(self.volume_menu)
+ self.volume_slider = QtGui.QSlider(QtCore.Qt.Horizontal)
+ self.volume_slider.setTickInterval(10)
+ self.volume_slider.setTickPosition(QtGui.QSlider.TicksAbove)
+ self.volume_slider.setMinimum(0)
+ self.volume_slider.setMaximum(100)
+ self.volume_slider.setTracking(True)
+ self.volume_slider.setToolTip(translate('OpenLP.SlideController', 'Audio Volume.'))
+ self.volume_slider.setValue(100)
+ self.volume_slider.setGeometry(QtCore.QRect(90, 160, 221, 24))
+ self.volume_slider.setObjectName('volume_slider')
+ self.volume_slider.valueChanged.connect(self.on_volume_change)
+ self.volume_action = QtGui.QAction(self.volume_menu)
+ self.volume_action.setText(translate('OpenLP.ServiceManager', '&Volume'))
+ self.volume_widget = QtGui.QWidgetAction(self.volume_action)
+ self.volume_widget.setDefaultWidget(self.volume_slider)
+ self.volume_menu.addAction(self.volume_widget)
self.menu.addSeparator()
self.preview_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'),
icon=':/general/general_preview.png', triggers=self.make_preview)
@@ -860,6 +907,8 @@
self.maintain_action.setVisible(False)
self.notes_action.setVisible(False)
self.time_action.setVisible(False)
+ self.playback_mode_menu.menuAction().setVisible(False)
+ self.volume_menu.menuAction().setVisible(False)
self.auto_start_action.setVisible(False)
if service_item['service_item'].is_capable(ItemCapabilities.CanEdit) and service_item['service_item'].edit_id:
self.edit_action.setVisible(True)
@@ -881,7 +930,12 @@
delay_suffix = ' ...'
self.timed_slide_interval.setText(
translate('OpenLP.ServiceManager', '&Delay between slides') + delay_suffix)
- # TODO for future: make group explains itself more visually
+ if service_item['service_item'].is_capable(ItemCapabilities.HasActionAtTheEnd):
+ self.auto_play_slides_next.setChecked(service_item['service_item'].action_at_the_end
+ == ActionAtTheEnd.Next)
+ self.auto_play_slides_next.setVisible(True)
+ else:
+ self.auto_play_slides_next.setVisible(False)
else:
self.auto_play_slides_menu.menuAction().setVisible(False)
if service_item['service_item'].is_capable(ItemCapabilities.HasVariableStartTime) and \
@@ -894,6 +948,16 @@
if service_item['service_item'].will_auto_start:
self.auto_start_action.setText(translate('OpenLP.ServiceManager', '&Auto Start - active'))
self.auto_start_action.setIcon(self.active)
+ if service_item['service_item'].is_capable(ItemCapabilities.HasActionAtTheEnd) and \
+ not service_item['service_item'].is_capable(ItemCapabilities.CanLoop):
+ action_at_the_end = service_item['service_item'].action_at_the_end
+ self.playback_mode_default.setChecked(action_at_the_end == ActionAtTheEnd.Nothing)
+ self.playback_mode_loop.setChecked(action_at_the_end == ActionAtTheEnd.Loop)
+ self.playback_mode_next.setChecked(action_at_the_end == ActionAtTheEnd.Next)
+ self.playback_mode_menu.menuAction().setVisible(True)
+ if service_item['service_item'].is_capable(ItemCapabilities.RequiresMedia):
+ self.volume_slider.setValue(service_item['service_item'].volume)
+ self.volume_menu.menuAction().setVisible(True)
if service_item['service_item'].is_text():
for plugin in self.plugin_manager.plugins:
if plugin.name == 'custom' and plugin.status == PluginStatus.Active:
@@ -942,10 +1006,15 @@
"""
item = self.find_service_item()[0]
service_item = self.service_items[item]['service_item']
- service_item.auto_play_slides_once = not service_item.auto_play_slides_once
+ if service_item.action_at_the_end == ActionAtTheEnd.Next:
+ service_item.auto_play_slides_once = True
+ service_item.action_at_the_end = ActionAtTheEnd.Nothing
+ else:
+ service_item.auto_play_slides_once = not service_item.auto_play_slides_once
if service_item.auto_play_slides_once:
service_item.auto_play_slides_loop = False
self.auto_play_slides_loop.setChecked(False)
+ self.auto_play_slides_next.setChecked(False)
if service_item.auto_play_slides_once and service_item.timed_slide_interval == 0:
service_item.timed_slide_interval = Settings().value(
self.main_window.general_settings_section + '/loop delay')
@@ -959,15 +1028,42 @@
"""
item = self.find_service_item()[0]
service_item = self.service_items[item]['service_item']
- service_item.auto_play_slides_loop = not service_item.auto_play_slides_loop
+ if service_item.action_at_the_end == ActionAtTheEnd.Next:
+ service_item.auto_play_slides_loop = True
+ service_item.action_at_the_end = ActionAtTheEnd.Nothing
+ else:
+ service_item.auto_play_slides_loop = not service_item.auto_play_slides_loop
if service_item.auto_play_slides_loop:
service_item.auto_play_slides_once = False
self.auto_play_slides_once.setChecked(False)
+ self.auto_play_slides_next.setChecked(False)
if service_item.auto_play_slides_loop and service_item.timed_slide_interval == 0:
service_item.timed_slide_interval = Settings().value(
self.main_window.general_settings_section + '/loop delay')
self.set_modified()
+ def toggle_auto_play_slides_next(self, field=None):
+ """
+ Toggle Auto play slide once and Go Live next item.
+ Inverts auto play once option for the item and sets action at the end to Next or Nothing
+
+ :param field:
+ """
+ item = self.find_service_item()[0]
+ service_item = self.service_items[item]['service_item']
+ service_item.auto_play_slides_once = not service_item.auto_play_slides_once
+ self.auto_play_slides_loop.setChecked(False)
+ self.auto_play_slides_once.setChecked(False)
+ if service_item.auto_play_slides_once:
+ service_item.auto_play_slides_loop = False
+ service_item.action_at_the_end = ActionAtTheEnd.Next
+ else:
+ service_item.action_at_the_end = ActionAtTheEnd.Nothing
+ if service_item.auto_play_slides_once and service_item.timed_slide_interval == 0:
+ service_item.timed_slide_interval = Settings().value(
+ self.main_window.general_settings_section + '/loop delay')
+ self.set_modified()
+
def on_timed_slide_interval(self, field=None):
"""
Shows input dialog for enter interval in seconds for delay
@@ -994,6 +1090,26 @@
service_item.auto_play_slides_once = False
self.set_modified()
+ def on_playback_mode_select(self, action):
+ """
+ Toggle playback mode.
+
+ :param field:
+ """
+ item = self.find_service_item()[0]
+ self.service_items[item]['service_item'].action_at_the_end = action.data()
+ self.set_modified()
+
+ def on_volume_change(self, value):
+ """
+ Change Audio volume value
+
+ :param value:
+ """
+ item = self.find_service_item()[0]
+ self.service_items[item]['service_item'].volume = value
+ self.set_modified()
+
def on_auto_start(self, field=None):
"""
Toggles to Auto Start Setting.
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2015-05-31 06:40:37 +0000
+++ openlp/core/ui/slidecontroller.py 2015-06-25 08:27:29 +0000
@@ -33,7 +33,7 @@
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
RegistryMixin, OpenLPMixin
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
- ScreenList, build_icon, build_html
+ ScreenList, build_icon, build_html, ActionAtTheEnd
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
from openlp.core.lib.ui import create_action
from openlp.core.utils.actions import ActionList, CategoryOrder
@@ -52,11 +52,15 @@
NARROW_MENU = [
'hide_menu'
]
-LOOP_LIST = [
+LOOP_LIST_TEXT = [
'play_slides_menu',
'loop_separator',
'delay_spin_box'
]
+ACTION_AT_THE_END = [
+ 'action_at_the_end_menu',
+ 'loop_separator',
+]
AUDIO_LIST = [
'audioPauseItem',
'audio_time_label'
@@ -275,12 +279,32 @@
self.play_slides_once = create_action(self, 'playSlidesOnce', text=UiStrings().PlaySlidesToEnd,
icon=':/media/media_time.png', checked=False, can_shortcuts=True,
category=self.category, triggers=self.on_play_slides_once)
+ self.play_slides_next = create_action(self, 'playSlidesNext', text=UiStrings().PlaySlidesToEndAndNext,
+ icon=':/media/media_time.png', checked=False, can_shortcuts=True,
+ category=self.category, triggers=self.on_play_slides_next)
if Settings().value(self.main_window.advanced_settings_section + '/slide limits') == SlideLimits.Wrap:
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
else:
self.play_slides_menu.setDefaultAction(self.play_slides_once)
self.play_slides_menu.menu().addAction(self.play_slides_loop)
self.play_slides_menu.menu().addAction(self.play_slides_once)
+ self.play_slides_menu.menu().addAction(self.play_slides_next)
+ self.action_at_the_end_menu = QtGui.QToolButton(self.toolbar)
+ self.action_at_the_end_menu.setObjectName('action_at_the_end_menu')
+ self.action_at_the_end_menu.setText(translate('OpenLP.SlideController', 'Play Slides'))
+ self.action_at_the_end_menu.setPopupMode(QtGui.QToolButton.MenuButtonPopup)
+ self.action_at_the_end_menu.setMenu(QtGui.QMenu(translate('OpenLP.SlideController', 'Play Slides'),
+ self.toolbar))
+ self.toolbar.add_toolbar_widget(self.action_at_the_end_menu)
+ self.action_at_the_end_loop = create_action(self, 'actionAtTheEndLoop', text=UiStrings().ActionAtTheEndLoop,
+ icon=':/media/media_time.png', checked=False, can_shortcuts=True,
+ category=self.category, triggers=self.on_action_at_the_end_loop)
+ self.action_at_the_end_next = create_action(self, 'actionAtTheEndNext', text=UiStrings().ActionAtTheEndNext,
+ icon=':/media/media_time.png', checked=False, can_shortcuts=True,
+ category=self.category, triggers=self.on_action_at_the_end_next)
+ self.action_at_the_end_menu.setDefaultAction(self.action_at_the_end_loop)
+ self.action_at_the_end_menu.menu().addAction(self.action_at_the_end_loop)
+ self.action_at_the_end_menu.menu().addAction(self.action_at_the_end_next)
# Loop Delay Spinbox
self.delay_spin_box = QtGui.QSpinBox()
self.delay_spin_box.setObjectName('delay_spin_box')
@@ -403,7 +427,8 @@
# Need to use event as called across threads and UI is updated
QtCore.QObject.connect(self, QtCore.SIGNAL('slidecontroller_toggle_display'), self.toggle_display)
Registry().register_function('slidecontroller_live_spin_delay', self.receive_spin_delay)
- self.toolbar.set_widget_visible(LOOP_LIST, False)
+ self.toolbar.set_widget_visible(LOOP_LIST_TEXT, False)
+ self.toolbar.set_widget_visible(ACTION_AT_THE_END, False)
self.toolbar.set_widget_visible(WIDE_MENU, False)
self.set_live_hot_keys(self)
self.__add_actions_to_widget(self.controller)
@@ -519,6 +544,8 @@
self.on_play_slides_loop(False)
elif self.play_slides_once.isChecked():
self.on_play_slides_once(False)
+ elif self.play_slides_next.isChecked():
+ self.on_play_slides_next(False)
def toggle_display(self, action):
"""
@@ -709,23 +736,33 @@
self.toolbar.hide()
self.mediabar.hide()
self.song_menu.hide()
- self.toolbar.set_widget_visible(LOOP_LIST, False)
+ self.toolbar.set_widget_visible(LOOP_LIST_TEXT, False)
+ self.toolbar.set_widget_visible(ACTION_AT_THE_END, False)
self.toolbar.set_widget_visible(['song_menu'], False)
# Reset the button
self.play_slides_once.setChecked(False)
self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_loop.setChecked(False)
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
+ self.play_slides_next.setChecked(False)
+ self.play_slides_next.setIcon(build_icon(':/media/media_time.png'))
if item.is_text():
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
not self.song_menu.menu().isEmpty()):
self.toolbar.set_widget_visible(['song_menu'], True)
- if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
- self.toolbar.set_widget_visible(LOOP_LIST)
+ if item.is_capable(ItemCapabilities.CanLoop):
+ if len(item.get_frames()) > 1:
+ self.toolbar.set_widget_visible(LOOP_LIST_TEXT)
+ if item.is_capable(ItemCapabilities.HasActionAtTheEnd):
+ self.play_slides_next.setVisible(True)
+ else:
+ self.play_slides_next.setVisible(False)
+ if not item.is_capable(ItemCapabilities.CanLoop) and item.is_capable(ItemCapabilities.HasActionAtTheEnd):
+ self.toolbar.set_widget_visible(ACTION_AT_THE_END)
if item.is_media():
self.mediabar.show()
- self.previous_item.setVisible(not item.is_media())
- self.next_item.setVisible(not item.is_media())
+ self.previous_item.setVisible(not item.is_media() or len(item.get_frames()) > 1)
+ self.next_item.setVisible(not item.is_media() or len(item.get_frames()) > 1)
# The layout of the toolbar is size dependent, so make sure it fits. Reset stored controller_width.
if self.is_live:
self.controller_width = -1
@@ -806,14 +843,29 @@
self.slide_selected()
else:
self._process_item(item, slide_num)
- if self.is_live and item.auto_play_slides_loop and item.timed_slide_interval > 0:
+ if self.is_live and item.auto_play_slides_loop and item.timed_slide_interval > 0 and \
+ item.action_at_the_end == ActionAtTheEnd.Nothing:
self.play_slides_loop.setChecked(item.auto_play_slides_loop)
self.delay_spin_box.setValue(int(item.timed_slide_interval))
self.on_play_slides_loop()
- elif self.is_live and item.auto_play_slides_once and item.timed_slide_interval > 0:
+ elif self.is_live and item.auto_play_slides_once and item.timed_slide_interval > 0 and \
+ item.action_at_the_end == ActionAtTheEnd.Nothing:
self.play_slides_once.setChecked(item.auto_play_slides_once)
self.delay_spin_box.setValue(int(item.timed_slide_interval))
self.on_play_slides_once()
+ elif self.is_live and item.auto_play_slides_once and item.timed_slide_interval > 0 and \
+ item.action_at_the_end == ActionAtTheEnd.Next:
+ self.play_slides_next.setChecked(item.auto_play_slides_once)
+ self.delay_spin_box.setValue(int(item.timed_slide_interval))
+ self.on_play_slides_next()
+ elif self.is_live and not item.is_capable(ItemCapabilities.CanLoop) and \
+ item.is_capable(ItemCapabilities.HasActionAtTheEnd):
+ if item.action_at_the_end == ActionAtTheEnd.Loop:
+ self.on_action_at_the_end_loop(True)
+ elif item.action_at_the_end == ActionAtTheEnd.Next:
+ self.on_action_at_the_end_next(True)
+ else:
+ self.on_action_at_the_end_loop(False)
def _process_item(self, service_item, slide_no):
"""
@@ -823,6 +875,7 @@
:param slide_no: The slide number to select
"""
self.on_stop_loop()
+ self.update_slide_limits()
old_item = self.service_item
# rest to allow the remote pick up verse 1 if large imaged
self.selected_row = 0
@@ -1208,7 +1261,8 @@
Toggles the loop state.
"""
hide_mode = self.hide_mode()
- if hide_mode is None and (self.play_slides_loop.isChecked() or self.play_slides_once.isChecked()):
+ if hide_mode is None and (self.play_slides_loop.isChecked() or self.play_slides_once.isChecked()
+ or self.play_slides_next.isChecked()):
self.on_start_loop()
else:
self.on_stop_loop()
@@ -1244,8 +1298,11 @@
self.play_slides_loop.setText(UiStrings().StopPlaySlidesInLoop)
self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
+ self.play_slides_next.setIcon(build_icon(':/media/media_time'))
+ self.play_slides_next.setText(UiStrings().PlaySlidesToEndAndNext)
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
self.play_slides_once.setChecked(False)
+ self.play_slides_next.setChecked(False)
else:
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
@@ -1267,13 +1324,102 @@
self.play_slides_once.setText(UiStrings().StopPlaySlidesToEnd)
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
+ self.play_slides_next.setIcon(build_icon(':/media/media_time'))
+ self.play_slides_next.setText(UiStrings().PlaySlidesToEndAndNext)
self.play_slides_menu.setDefaultAction(self.play_slides_once)
self.play_slides_loop.setChecked(False)
+ self.play_slides_next.setChecked(False)
else:
self.play_slides_once.setIcon(build_icon(':/media/media_time'))
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.on_toggle_loop()
+ def on_play_slides_next(self, checked=None):
+ """
+ Start or stop 'Play Slides to End'
+
+ :param checked: is the check box checked.
+ """
+ if checked is None:
+ checked = self.play_slides_next.isChecked()
+ else:
+ self.play_slides_next.setChecked(checked)
+ self.log_debug('on_play_slides_next %s' % checked)
+ if checked:
+ self.play_slides_next.setIcon(build_icon(':/media/media_stop.png'))
+ self.play_slides_next.setText(UiStrings().StopPlaySlidesToEnd)
+ self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
+ self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
+ self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
+ self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
+ self.play_slides_menu.setDefaultAction(self.play_slides_next)
+ self.play_slides_once.setChecked(False)
+ self.play_slides_loop.setChecked(False)
+ self.slide_limits = SlideLimits.Next
+ else:
+ self.play_slides_next.setIcon(build_icon(':/media/media_time'))
+ self.play_slides_next.setText(UiStrings().PlaySlidesToEndAndNext)
+ self.update_slide_limits()
+ self.on_toggle_loop()
+
+ def on_toggle_action_at_the_end(self):
+ """
+ Toggles the action at the end.
+ """
+ if self.action_at_the_end_loop.isChecked():
+ self.service_item.action_at_the_end = ActionAtTheEnd.Loop
+ elif self.action_at_the_end_next.isChecked():
+ self.service_item.action_at_the_end = ActionAtTheEnd.Next
+ else:
+ self.service_item.action_at_the_end = ActionAtTheEnd.Nothing
+ self.media_controller.update_action_at_the_end(self.service_item.action_at_the_end)
+
+ def on_action_at_the_end_loop(self, checked=None):
+ """
+ Start or stop 'Play in Loop'
+
+ :param checked: is the check box checked.
+ """
+ if checked is None:
+ checked = self.action_at_the_end_loop.isChecked()
+ else:
+ self.action_at_the_end_loop.setChecked(checked)
+ self.log_debug('on_action_at_the_end_loop %s' % checked)
+ if checked:
+ self.action_at_the_end_loop.setIcon(build_icon(':/media/media_stop.png'))
+ self.action_at_the_end_loop.setText(UiStrings().StopPlaySlidesInLoop)
+ self.action_at_the_end_next.setIcon(build_icon(':/media/media_time.png'))
+ self.action_at_the_end_next.setText(UiStrings().ActionAtTheEndNext)
+ self.action_at_the_end_menu.setDefaultAction(self.action_at_the_end_loop)
+ self.action_at_the_end_next.setChecked(False)
+ else:
+ self.action_at_the_end_loop.setIcon(build_icon(':/media/media_time.png'))
+ self.action_at_the_end_loop.setText(UiStrings().ActionAtTheEndLoop)
+ self.on_toggle_action_at_the_end()
+
+ def on_action_at_the_end_next(self, checked=None):
+ """
+ Start or stop 'Play to end and Go Live Next item'
+
+ :param checked: is the check box checked.
+ """
+ if checked is None:
+ checked = self.action_at_the_end_next.isChecked()
+ else:
+ self.action_at_the_end_next.setChecked(checked)
+ self.log_debug('on_action_at_the_end_next %s' % checked)
+ if checked:
+ self.action_at_the_end_next.setIcon(build_icon(':/media/media_stop.png'))
+ self.action_at_the_end_next.setText(UiStrings().StopPlaySlidesToEnd)
+ self.action_at_the_end_loop.setIcon(build_icon(':/media/media_time.png'))
+ self.action_at_the_end_loop.setText(UiStrings().ActionAtTheEndLoop)
+ self.action_at_the_end_menu.setDefaultAction(self.action_at_the_end_next)
+ self.action_at_the_end_loop.setChecked(False)
+ else:
+ self.action_at_the_end_next.setIcon(build_icon(':/media/media_time'))
+ self.action_at_the_end_next.setText(UiStrings().ActionAtTheEndNext)
+ self.on_toggle_action_at_the_end()
+
def set_audio_items_visibility(self, visible):
"""
Set the visibility of the audio stuff
@@ -1300,7 +1446,10 @@
:param event: The triggered event
"""
if event.timerId() == self.timer_id:
- self.on_slide_selected_next(self.play_slides_loop.isChecked())
+ if self.play_slides_next.isChecked():
+ self.on_slide_selected_next()
+ else:
+ self.on_slide_selected_next(self.play_slides_loop.isChecked())
def on_edit_song(self, field=None):
"""
=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py 2015-05-25 20:37:29 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py 2015-06-25 08:27:29 +0000
@@ -848,6 +848,7 @@
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanWordSplit)
service_item.add_capability(ItemCapabilities.CanEditTitle)
+ service_item.add_capability(ItemCapabilities.HasActionAtTheEnd)
# Service Item: Title
service_item.title = '%s %s' % (verses.format_verses(), verses.format_versions())
# Service Item: Theme
=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
--- openlp/plugins/custom/lib/mediaitem.py 2015-04-02 20:49:19 +0000
+++ openlp/plugins/custom/lib/mediaitem.py 2015-06-25 08:27:29 +0000
@@ -220,6 +220,7 @@
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanSoftBreak)
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
+ service_item.add_capability(ItemCapabilities.HasActionAtTheEnd)
custom_slide = self.plugin.db_manager.get_object(CustomSlide, item_id)
title = custom_slide.title
credit = custom_slide.credits
=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py 2015-05-16 21:24:57 +0000
+++ openlp/plugins/images/lib/mediaitem.py 2015-06-25 08:27:29 +0000
@@ -558,6 +558,7 @@
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.HasThumbnails)
+ service_item.add_capability(ItemCapabilities.HasActionAtTheEnd)
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
=== modified file 'openlp/plugins/media/lib/mediaitem.py'
--- openlp/plugins/media/lib/mediaitem.py 2015-05-28 20:38:43 +0000
+++ openlp/plugins/media/lib/mediaitem.py 2015-06-25 08:27:29 +0000
@@ -171,8 +171,14 @@
player = get_media_players()[0]
if index == 0:
set_media_players(player)
+ can_play_list = False
+ for player_name in player:
+ if self.media_controller.media_players[player_name].can_play_list:
+ can_play_list = True
+ self.single_service_item = can_play_list
else:
set_media_players(player, player[index - 1])
+ self.single_service_item = self.media_controller.media_players[player[index - 1]].can_play_list
def on_reset_click(self):
"""
@@ -225,13 +231,31 @@
:param remote: Triggered from remote
:param context: Why is it being generated
"""
- if item is None:
- item = self.list_view.currentItem()
- if item is None:
+ if item:
+ items = [item]
+ else:
+ if self.single_service_item:
+ items = self.list_view.selectedItems()
+ else:
+ items = [self.list_view.currentItem()]
+ if not items:
return False
- filename = item.data(QtCore.Qt.UserRole)
- # Special handling if the filename is a optical clip
- if filename.startswith('optical:'):
+ has_optical = False
+ filenames = []
+ for item in items:
+ filename = item.data(QtCore.Qt.UserRole)
+ filenames.append(filename)
+ # Special handling if the filename is a optical clip
+ if filename.startswith('optical:'):
+ has_optical = True
+ break
+ if has_optical and len(items) > 1:
+ # Optical disc is no longer present
+ critical_error_message_box(
+ translate('MediaPlugin.MediaItem', 'Select only one optical itme'),
+ translate('MediaPlugin.MediaItem', 'Cannot select multiple optical disc items.'))
+ return False
+ if has_optical:
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(filename)
if not os.path.exists(name):
if not remote:
@@ -250,22 +274,50 @@
service_item.end_time = end / 1000
service_item.add_capability(ItemCapabilities.IsOptical)
else:
- if not os.path.exists(filename):
+ missing_items_file_names = []
+ existing_file_names = []
+ for filename in filenames:
+ if os.path.exists(filename):
+ existing_file_names.append(filename)
+ else:
+ missing_items_file_names.append(filename)
+ if not existing_file_names and missing_items_file_names:
if not remote:
# File is no longer present
critical_error_message_box(
- translate('MediaPlugin.MediaItem', 'Missing Media File'),
- translate('MediaPlugin.MediaItem', 'The file %s no longer exists.') % filename)
- return False
- (path, name) = os.path.split(filename)
- service_item.title = name
+ translate('MediaPlugin.MediaItem', 'Missing Media File(s)'),
+ translate('MediaPlugin.MediaItem', 'The following file(s) no longer exist: %s')
+ % '\n'.join(missing_items_file_names))
+ return False
+ elif missing_items_file_names and QtGui.QMessageBox.question(
+ self, translate('MediaPlugin.MediaItem', 'Missing Media file(s)'),
+ translate('MediaPlugin.MediaItem', 'The following Media file(s) no longer exist: %s\n'
+ 'Do you want to add the other files anyway?') % '\n'.join(missing_items_file_names),
+ QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == \
+ QtGui.QMessageBox.No:
+ return False
service_item.processor = self.display_type_combo_box.currentText()
- service_item.add_from_command(path, name, CLAPPERBOARD)
+ if len(existing_file_names) == 1 and not service_item.title:
+ (path, name) = os.path.split(existing_file_names[0])
+ service_item.title = name
+ elif not service_item.title:
+ service_item.title = str(self.plugin.name_strings['plural'])
+ service_item.add_capability(ItemCapabilities.HasActionAtTheEnd)
+ if self.single_service_item:
+ service_item.add_capability(ItemCapabilities.CanMaintain)
+ service_item.add_capability(ItemCapabilities.CanAppend)
+ for filename in existing_file_names:
+ (path, name) = os.path.split(filename)
+ service_item.add_from_command(path, name, CLAPPERBOARD)
# Only get start and end times if going to a service
if context == ServiceItemContext.Service:
# Start media and obtain the length
if not self.media_controller.media_length(service_item):
return False
+ if hasattr(self, 'media_controller') and hasattr(self.media_controller, 'live_controller'):
+ service_item.volume = self.media_controller.live_controller.volume_slider.value()
+ else:
+ service_item.volume = 100
service_item.add_capability(ItemCapabilities.CanAutoStartForLive)
service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.RequiresMedia)
@@ -310,14 +362,19 @@
used_players, override_player = get_media_players()
media_players = self.media_controller.media_players
current_index = 0
+ can_play_list = False
for player in used_players:
# load the drop down selection
self.display_type_combo_box.addItem(media_players[player].original_name)
if override_player == player:
current_index = len(self.display_type_combo_box)
+ can_play_list = media_players[player].can_play_list
+ if (not override_player or override_player == 'auto') and media_players[player].can_play_list:
+ can_play_list = True
if self.display_type_combo_box.count() > 1:
self.display_type_combo_box.insertItem(0, self.automatic)
self.display_type_combo_box.setCurrentIndex(current_index)
+ self.single_service_item = can_play_list
if override_player:
self.media_widget.show()
else:
=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py 2015-01-18 13:39:21 +0000
+++ openlp/plugins/songs/lib/mediaitem.py 2015-06-25 08:27:29 +0000
@@ -422,6 +422,7 @@
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
service_item.add_capability(ItemCapabilities.AddIfNewItem)
service_item.add_capability(ItemCapabilities.CanSoftBreak)
+ service_item.add_capability(ItemCapabilities.HasActionAtTheEnd)
song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name
service_item.edit_id = item_id
=== modified file 'tests/functional/openlp_core_ui_media/test_vlcplayer.py'
--- tests/functional/openlp_core_ui_media/test_vlcplayer.py 2015-05-25 20:58:05 +0000
+++ tests/functional/openlp_core_ui_media/test_vlcplayer.py 2015-06-25 08:27:29 +0000
@@ -346,7 +346,7 @@
mocked_controller = MagicMock()
mocked_controller.media_info.volume = 100
mocked_controller.media_info.media_type = MediaType.Video
- mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
+ mocked_controller.media_info.file_info[0].absoluteFilePath.return_value = media_path
mocked_vlc_media = MagicMock()
mocked_media = MagicMock()
mocked_media.get_duration.return_value = 10000
@@ -386,7 +386,7 @@
mocked_controller = MagicMock()
mocked_controller.media_info.volume = 100
mocked_controller.media_info.media_type = MediaType.CD
- mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
+ mocked_controller.media_info.file_info[0].absoluteFilePath.return_value = media_path
mocked_controller.media_info.title_track = 1
mocked_vlc_media = MagicMock()
mocked_media = MagicMock()
@@ -432,7 +432,7 @@
mocked_controller = MagicMock()
mocked_controller.media_info.volume = 100
mocked_controller.media_info.media_type = MediaType.CD
- mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
+ mocked_controller.media_info.file_info[0].absoluteFilePath.return_value = media_path
mocked_controller.media_info.title_track = 1
mocked_vlc_media = MagicMock()
mocked_media = MagicMock()
@@ -478,7 +478,7 @@
mocked_controller = MagicMock()
mocked_controller.media_info.volume = 100
mocked_controller.media_info.media_type = MediaType.CD
- mocked_controller.media_info.file_info.absoluteFilePath.return_value = media_path
+ mocked_controller.media_info.file_info[0].absoluteFilePath.return_value = media_path
mocked_controller.media_info.title_track = 1
mocked_vlc_media = MagicMock()
mocked_media = MagicMock()
References