Merge lp:~crichter/openlp/media_rewrite into lp:openlp

Proposed by rimach
Status: Merged
Merged at revision: 1810
Proposed branch: lp:~crichter/openlp/media_rewrite
Merge into: lp:openlp
Diff against target: 3216 lines (+1924/-669)
18 files modified
openlp/core/lib/htmlbuilder.py (+3/-107)
openlp/core/lib/mediaplayer.py (+137/-0)
openlp/core/lib/plugin.py (+1/-1)
openlp/core/lib/renderer.py (+2/-2)
openlp/core/ui/__init__.py (+2/-2)
openlp/core/ui/maindisplay.py (+67/-208)
openlp/core/ui/mainwindow.py (+3/-0)
openlp/core/ui/media/__init__.py (+65/-0)
openlp/core/ui/media/mediacontroller.py (+576/-0)
openlp/core/ui/media/phononplayer.py (+201/-0)
openlp/core/ui/media/webkitplayer.py (+426/-0)
openlp/core/ui/slidecontroller.py (+70/-122)
openlp/plugins/media/lib/mediaitem.py (+125/-55)
openlp/plugins/media/lib/mediatab.py (+159/-29)
openlp/plugins/media/mediaplugin.py (+43/-48)
resources/forms/mediafilesdialog.ui (+0/-95)
resources/pyinstaller/hook-openlp.core.ui.media.py (+30/-0)
scripts/windows-builder.py (+14/-0)
To merge this branch: bzr merge lp:~crichter/openlp/media_rewrite
Reviewer Review Type Date Requested Status
Raoul Snyman Approve
Tim Bentley Approve
Jonathan Corwin Pending
Andreas Preikschat Pending
Review via email: mp+84155@code.launchpad.net

This proposal supersedes a proposal from 2011-11-30.

Description of the change

Rewrite of the multimedia stuff.
- add separated multimedia code in own subdirectory in openlp/ui

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

51 + if (navigator.appName.indexOf("Microsoft Internet")==-1)
52 + {
53 + if (document.embeds && document.embeds[movieName])
54 + return document.embeds[movieName];
55 + }

Please remove all traces of stuff that references Internet Explorer. We are using WebKit, this code doesn't need to be here and only serves to add bloat where it is not needed.

616 + def sendToPlugins(self, v1=None, v2=None, v3=None, v4=None, v5=None):

What is "v1", "v2", etc? They don't make sense. If you need to add multiple arguments and you don't know how many, either use *args or **kwargs.

767 +class MediaAPIs(object):

Enumeration objects should be singular ("MediaAPI"). You can also combine the enumeration and the actual MediaAPI object (Qt does this).

This is all I have to say so far, without further testing. I'll have a look again at home.

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Plugin code is working the wrong way.
Plugins should hook into core code not other way round.
The code in Mainwindows is incorrect and the plugin should look for preview and live controls.

review: Needs Fixing
Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

Rather than have the MediaAPI's hardcoded (e.g. line 1095) it would be good (if possible) if you could dynamically load them and remove any direct reference to VLC/Phonon/Webkit from outside their individual classes.

This is how presentations work. It also allows someone to just drop a new py file inheriting MediaAPI in the folder and it would just work without needing to change any other code.

Presentations code:
http://bazaar.launchpad.net/~openlp-core/openlp/trunk/view/head:/openlp/plugins/presentations/presentationplugin.py#L111

Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

Something else to think about, moving the webkit media out of htmlbuilder and pulling it in.

So in htmlbuilder.py, consider putting three more placeholders in the large HTML string for media CSS, media JS and media HTML.
Now instead of having the media css/html/js in there, move this to the webkit mediaapi item.

Then get htmlbuilder to request the above code to put in place.

So pseudo code with made up variable names...

HTMLSRC = "<style>...%s...</style><script>...%s...</script><body>...%s...</body>"

for plugin in self.parent.plugins:
   css += plugin.display_css()
   js += plugin.display_javascript()
   html += plugin.display_html()

html = HTMLSRC % (css, js, html)

The media plugin can then do similar, looping around the MediaAPI's to get the relevant code.

We could then use this framework later to move the Alert HTML into the alert plugin, and should milleja46 decide to create his Countdown plugin, he will have a way of producing the relevant div's he'll need too.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

plugin.display_css()
plugin.display_javascript()
plugin.display_html()

1. These functions are poorly named. What does "display_css" mean? Does this function actually display CSS? Surely these functions should rather be called "get_css" or "get_display_css"

2. Plugin is a QObject-based class, therefore, like the rest of the functions in the Plugin class, your display_* functions should be getDisplayCss, getDisplayJavaScript and getDisplayHtml.

review: Needs Fixing
Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

Lines 9 -> 17 need to be moved to the [renamed] webkit display_css() too.

(I'm aware you just copied my function names, but I was just doing some quick examples and not putting any thought into the naming :)

A few cosmetics:

Delete commented out code: 285,286,300,318,756,1109,1355,1356,1873,1874,2245,2246,2251,2252,2454
Note, if they are commented out because you plan to implement properly in a future commit, just put a # TODO: comment above them, saying what the missing functionality is. We don't like to merge commented out code into trunk.

Lines too long: 353, 552
Space needed before the +=: 1269, 1278,1287
Lines 1688 -> 1698: You need a space on both sides of the + or -

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

The plugin to core code is wrong and I think it is because we have the placement wrong.
A lot of the plumbing is because the players are controlled by the plugin and core needs access to them.
For presentations this as fine as the players were external but for this it's more complicated.
I think Core should provide player services (extensible and flexible so more can be added). This should stop the needed to pass controllers around.
The media plugin asks what players can I use and what do I want to us or just does not care and uses what core has decided.
I am not sure what will happen if this code has media deactivated.

review: Needs Fixing
Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

The core devs have had a brief chat about this and this has highlighted possible differences of opinion between us. So we plan to put aside some time to talk about this on IRC. However because of various commitments and holidays, this isn't likely to happen until after beta2 has released,

Once we come to a common agreement, we can then present you with a single direction, rather than four different people telling you potentially four conflicting things which doesn't really help anyone!

Some of the things we will discuss are likely to be:
1. Future enhancements such as linking songs to audio (backing tracks) and adding a video background to a theme, should be able to hook into the media displays without too much effort. Backing tracks would obviously need a way of pulling in the media controls cleanly. Some of this may determine how much code is in core and how much is in the media plugin.

2. If the media plugin is disabled, OpenLP should still function perfectly well, so what should happen about the items in #1

3. Plugins should not be accessing core classes/methods directly. I.e. we don't want a method in a plugin to just call something like mainwindow.livecontroller.doSomething(). We need a documented proper api. Signals are usually the preferred method for sending data/performing actions, but we also need to consider the occasions when a plugin needs to retrieve data from core.

4. Whether plugins should be adding items to the controller toolbar, or whether these should be provided by core, and the best way of doing whichever option we choose.

And no doubt other things!

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

Hello Tim, hello Jonathan!

Thanks for your comments and inspecially your explanations Jonathan.
Unfortunately at home it is rarely possible for me to talk in the chat. But maybe if you plan a fix date for speaking regarding this topic, I could organize some time for this? (not on a thursday or sunday please)?
In the meantime I will think about this and put some ideas in the wiki. (http://wiki.openlp.org/Scratchpad:Media_Plugin_Rewrite)

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

only time for a quick run but initial comments.

I have a white rectangle on the live and preview displays which obscures the previews.
My test videos played OK and text over video worked.
I changed the video background and I got a segment fault.
The videos played with the global theme as background instead of black!.
Add any media files to servicemanage crashes with a unicode error (menu or DND). Note this code is changing in my B1 tree.

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> I have a white rectangle on the live and preview displays which obscures the
> previews.
-> removed
> I changed the video background and I got a segment fault.
-> may be fixed, but could you please retest and give me a little description which steps I have to do for this?
The videos played with the global theme as background instead of black!.
-> Hopefully fixed
> Add any media files to servicemanage crashes with a unicode error (menu or DND). Note this code is changing in my B1 tree.
-> Fixed, but currently the configurable Start/Stop Time doesnt work, because of missing implementation for getting the media length.

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

- Start Time implementation added

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Preview image 1/4 of the size of the box.

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Btw, I assigned you to bug #813995 "opengl rendering".

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

@Tim: Preview image size corrected

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

162 - Plugins should push stuff in and core save it. Core should not go and grab it. If they are media plugins then we need to new name a plugins will be confussing. It has for me!

No check on inactive plugins. This should be added and removed when plugins are activated / deactivated. Unless covered by above

766 is a python object so you should have get_display_CSS also 864 and 871
862 868 duplicate blank lines
920 MediaManager is python so need _'s in method names
949 # Signals spaced needed. In many places.
977 this is for plugins not core so it necessary change name.

Toolbar you do not need to next / prev slides so these nned to be removed.

1815 should be indented.
1963 why do we need to add extra layer of object?

2275 Line length. No lines over 80 characters
2422 blank line

2582 why remove mediatab

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

Hi Tim, thanks for your review! Here some comments from my side of view.

> 162 - Plugins should push stuff in and core save it. Core should not go and
> grab it. If they are media plugins then we need to new name a plugins will be
> confussing. It has for me!
I found no better solution till now :-(, because of the media stuff is used also if the media plugin is deactivated.
This stuff could also exist in other plugins, e.g. Alarm or later on a countdown plugin, ...
>
> No check on inactive plugins. This should be added and removed when plugins
> are activated / deactivated. Unless covered by above
Because of the the same code is used for background video, there should be no check for activated/deactivated plugins.
> Toolbar you do not need to next / prev slides so these nned to be removed.
What do you mean here?
> 1963 why do we need to add extra layer of object?
In my opionion its easier to handle (hide/show) if we have more controls later on (eg. DVD controls)
> 2582 why remove mediatab
I thought, I have to remove it because the mediatab now is handled seperately with getSettingsTab, isnt it?

> 766 is a python object so you should have get_display_CSS also 864 and 871
> 862 868 duplicate blank lines
> 920 MediaManager is python so need _'s in method names
> 949 # Signals spaced needed. In many places.
> 977 this is for plugins not core so it necessary change name.
> 1815 should be indented.
> 2275 Line length. No lines over 80 characters
> 2422 blank line
I will change these issues.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

I think the term plugins is what has caused some of the confusion.
Could we use the term "players" for the media players, Htmlplayer PhononPlayer VLCPlayer.
This would then leave plugins to their existing meaning. This would then make things clearer as we would have player lists and plugin lists and their meaning would be clear.
You may think of a better term than player but not plugin! It needs to be new.

When you add the new toolbar you keep the existing toolbar. That has all the existing features even if they are not relevant. The Green arrows are not relevant.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Player is good. The presentations plugin calls its things Controllers, but I don't think that quite fits here.

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

I'm a bit slow today :-(

What does your mean?
I thought the problem are the line 162ff
162 + plugin_css = u''
163 + plugin_js = u''
164 + plugin_html = u''

But I cant see any relation to the last two comments (Tims and Raouls)
Please could you explain it a little bit?

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Yes that is correct.
Any thing which comes from core/ui/media is a player thing.

This means the above lines are player_css/js/html as they are managed by the players.

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

The css/js/html stuff can delivered also from every plugin.
E.g. the alarm plugin should later this stuff deliver in this manner.
So this is the css/js/html part of any plugin which is webview related.

I have currently no other idea for a good title for this :-(

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

Please could you vote for a good variable name and an appropriate function title?

- plugins_webkit_css += plugin.getWebkitCss()
- css_from_plugins += plugin.getDisplayCss()
- css_additions += plugin.getAdditionalCss()

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Just a couple of small things as I browse through this:

215 + def getDisplayJavascript(self):

Technically, it is "JavaScript", not "Javascript"

329 + self.webView.settings().setAttribute(7, True)
394 + self.webView.settings().setAttribute(7, True)

What does the 7 stand for? Is there an enumeration you could use?

439 + or not self.isVisible():# or self.videoWidget.isVisible():

Please either remove the commented part, or put it on its own line.

754 === added file 'openlp/core/ui/media/mediaapi.py'

(Optional) You can also name it "media_api.py" if you want. It's a little easier to read :-)

734 + Cd = 3
735 + Dvd = 4

CD and DVD can be all capital letters, it also makes it slightly easier to read.

789 + Specialiced Media API class
790 + to reflect Features of the related API

"A generic media API class to provide OpenLP with a pluggable media display framework."

938 +class MediaManager(object):

Can we rather name this the MediaController? We already have another class named "MediaManager" and I don't want developers to get confused by the two.

1414 + Specialiced MediaAPI class
1415 + to reflect Features of the Phonon API

"A specialised version of the MediaAPI class, which provides a Phonon display."

1633 + Specialiced MediaAPI class
1634 + to reflect Features of the QtWebkit API

"A specialised version of the MediaAPI class, which provides a QtWebKit display."

2028 + sender = self.sender().objectName() or self.sender().text()

This is the old-style ternary operator shortcut for Python. Rather use the new one, which is a little longer, but is much more accurate: sender = self.sender().objectName() if self.sender().objectName() else self.sender().text()

2386 + #(path, name) = os.path.split(filename)

If it's commented out, and you're not going to use it, remove it.

Phew, that's all I have time for right now.

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Line 143 and 410: No \ needed.
Line 455/456: The operator should be before the \
if bla and \
    foo:

instead of

if bla \
    and foo:

When I hide the display (Hide Screen) and send a video live, the diplay is shown. But it should not be shown. (Single monitor set up)

Line 468: You can remove "shrinkItem" and use "self" instead.
Line 744/754: We have two blank lines between classes.
Line 958: Don't use a new line for each sentence, instead add full stops after each one and wrap the sentence if necessary. Also the syntax is not correct (the enumeration with "-" will not work). I suggest this: http://rst.ninjs.org/?n=9abe8c216bafbd786601f2ef62a9d8a8&theme=basic
Line 981 (and following): self.Time should be self.time

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Getting much better.
Need to roll with head.
Video works and plays.
Video preview does not work you just get the last theme used.
Video override for songs works but when the video finishes is drops back to theme. Video should loop?
Blank to screen blanks to theme. The rest work.
Preview bar needs to loose green arrows in the same way live did.
Need tool tips on the sliders.
Slides are not aligned.

review: Needs Fixing
Revision history for this message
matysek (mzibricky) wrote : Posted in a previous version of this proposal

This branch does not start when windows build is created:

http://pastebin.com/QLLBWDqY

The issue is that mediacontroller is looking for some python modules ending with 'api.py'. But these files are not available when a build is created.

We should find another way how to check available apis without looking directly on file system or we would need to include api.py files with the build.

Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Please merge trunk and resubmit. When will you continue to work on this?

review: Needs Resubmitting
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> Please merge trunk and resubmit. When will you continue to work on this?

Hopefully I can do some progress this week!

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

rimach this needs to be in by the end of November (in my view) if we release the next version at the end of the year.
I have taken some of your framework changes and are in the processing of merging them (with other fixes they were dependent on) so have a look at my merge request.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Missing and incomplete comments
; at the end of a number of lines.

Need to respin after my changes go in as they will be changes.

webkitapi.py has constants which should be in CAPITALS.

Check formatting (double blank lines) and field names there are naming standard errors.

Lots of commented out code some of which is your machine specific!

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Needs Fixing:
Please add more detailed documentation to the Display and MainDisplay classes. First I thought you were just splitting the MainDisplay class (withought seeing the use of it). Later I saw that you are actually creating Display objects. I had to look through the diff to confirm my guess (that it is used to as preview windows).

Do not do this:
    The implementation of the Media Controller
    The Media Controller adds an own class for every API
    Currently these are QtWebkit, Phonon and planed Vlc.

Do this instead:
    The implementation of the Media Controller. More text until the limit of 80
    characters is reached. The Media Controller adds an own class for every API.
    Currently these are QtWebkit, Phonon and planed Vlc.

It is much easier to read the docs in the code, when you do this.

(There is a option in Eric which may be helpful. It changes the background color of the relevant characters when you go beyond the allowed character length.)

Always end the sentence with a period. Look at this (periods missing): http://rst.ninjs.org/?n=1107f90fd81d51fbc803347489fc6a1b&theme=basic

The signals names need to be fixed (line 933ff): "Media Stop" vs "seekSlider" vs "media_hide".

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Initial comments
build_alert_css needs to be removed it is not called.
Why are the plugins added to the Renderer they are not used.

I will have a longer look tonight

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

http://builds.projecthq.biz/media_rewrite-OpenLP-1.9.7-bzr1490-setup.exe
Traceback (most recent call last):
  File "<string>", line 42, in <module>
  File "D:\OpenLP_Code\media_rewrite\build\pyi.win32\OpenLP\outPYZ1.pyz\openlp.core", line 291, in main
  File "D:\OpenLP_Code\media_rewrite\build\pyi.win32\OpenLP\outPYZ1.pyz\openlp.core", line 130, in run
  File "D:\OpenLP_Code\media_rewrite\build\pyi.win32\OpenLP\outPYZ1.pyz\openlp.core.ui.mainwindow", line 561, in __init__
  File "D:\OpenLP_Code\media_rewrite\build\pyi.win32\OpenLP\outPYZ1.pyz\openlp.core.ui.media.mediacontroller", line 57, in __init__
  File "D:\OpenLP_Code\media_rewrite\build\pyi.win32\OpenLP\outPYZ1.pyz\openlp.core.ui.media.mediacontroller", line 102, in check_available_media_apis
WindowsError: [Error 3] Das System kann den angegebenen Pfad nicht finden: u'C:\\Program Files (x86)\\OpenLP\\core\\ui\\media\\*.*'
None

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Tried running the code on linux and had a 10px border down the right and left side of the main display so I could see the slide below.

Need to update resources pyinstaller and create hook-openlp.plugins.media.core.ui.media.py file (check path)

ui/__init__ why add Display and Controller they are internal.

Display and Maindisplay have duplicate set up code for QTWebKit objects.

Alerts no longer get add over the top of videos.

media/__init__ imports should be at the top.

766 MediaAPI should be lowercase and any over calls. This will stop case issues in the future.

922 self.APIs can we be more explicit with the name. What mediaPlayers for example.

925 currentDisplayMediaPlayer !

977 does media_api.py need to be in that directory?

992 good example of why we need to rename.

999 is the comment correct.

1011 should there be a return here? Then we could loose the confusing isAnyonePlaying. If they are just leave. The stop would be unconditional.

1113 - 1115 ?????????????

1434 should be a constant at top of class as duplicated.

1643 etc why split out the flash CSS javascript etc.

1871,1878 double blank line

2025 why Display

2404 etc name verticalLayout_X with a name

2620 Are all these translates going to be used?

2722 Commented out code

2794 "Chose Player" ?? need to check with Raoul

2811 Commented code

General comment use of API in variable names should be resisted. Say what they are.

2992 how do we get here ?

MediaOpenForm Cannot show this!

Hook-open.media,py is wrong very wrong!

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

It will be easier to see what you have changed when you merge and commit trunk first and then fix your code.

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> ui/__init__ why add Display and Controller they are internal.
the display classes will be also used for plugin.media and later possibly also for thememanager

> 1113 - 1115 ?????????????
some media types need special controls here this could be added depended at the related Media type and Player type

> 2025 why Display
Display will be used to create the preview Display

> 2404 etc name verticalLayout_X with a name
> 2620 Are all these translates going to be used?
> 2992 how do we get here ?
> MediaOpenForm Cannot show this!
> 2811 Commented code
This peace of code is currently not yet active
I did some implemention for opening a DVD or a complete folder, but decided to wait with further steps after integration of the current stuff,
because Im afraid this media implementation beast would grow and grow and never integrated

> 1643 etc why split out the flash CSS javascript etc.
The flash related stuff is slightly different to the video stuff and the split offers the possibility to switch of this partially.
(its not needed, but it helps in case of bugfixing issues related to Flash OR Video)

> 977 does media_api.py need to be in that directory?
may we could also move this file to core/lib directory?

> 2794 "Chose Player" ?? need to check with Raoul
Sure, could you help please Raoul?

> 925 currentDisplayMediaPlayer !
No, as far as I understand phonon is not a special player. It an API for various installed Media Players. Thatswhy I used the term media api instead of media player all the time!

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Responding to you comments but not reviewing the code.

Possible usage is no reason to put things in now. Put them in when they are needed.

If code cannot be run please remove it so we can see and test what is going to be implemented. The new dialog is case here. Same with the Flash stuff.

move media_api.py

I am not happy with api as a name for a variable. Phonon is an abstraction layer but so is vlc. API in the terms of a variable name is meaningless.

Please can you make sure changes and head merges are in separate commits as per Andrea's comment.

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Please set screen to {} and not None:
270 + self.screen = None

review: Needs Fixing
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Does the preview (the little frame) works as good as in trunk without the changes you made to previewDisplay?

review: Needs Information
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> Does the preview (the little frame) works as good as in trunk without the
> changes you made to previewDisplay?

I'm not sure what you meant.
The preview is changed now for video. (in trunk there is only a widget for phonon, here there is a display widget which has webview, phonon widget, ...)
This widget should be disabled for other media types (images, songs, ...)

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Modules should not have word separators:
  openlp/core/lib/media_player.py
Should be:
  mediaplayer.py

I have VLC installed, but the VLC media controller did not show up. Is there a way to make it appear?

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

VLC can be tested with my media branch!
(it was a request from another core developer to split the stuff, to minimize the complexity. So I do all the changes at the media branch and merge it afterwards to the media_rewrite branch)

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Slide controller seems to be broken.
Add a multi verse song and then add a video. The verse numbers are not reset but left as per the previous song.

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> Slide controller seems to be broken.
> Add a multi verse song and then add a video. The verse numbers are not reset
> but left as per the previous song.
Please could you add more information regarding this issue. I tried it at Windows XP and Ubuntu Oneiric. No problems here.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

It is difficult to test as I have a broken box.
I have no webkit playback so I cannot test the code so text over video / alerts are a problem.

855 needs to be a to of class
1313 needs a comment as to what [1] means. There are other places as well.
1508 Duplicate List
1844 Subset of Duplicate List
2177 Comment not needed

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Line 2407 - how do I get this to work?
Preview summary in slide controller is in the wrong place.

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

Thanks for review!

> 855 needs to be a to of class
Im not understand this comment

> Line 2407 - how do I get this to work?
This is "only" a temporarily controller, which will used at loading of media file internally. (no really user interaction possible/needed, but needed to satisfy the interfaces)

> Preview summary in slide controller is in the wrong place.
What do you mean?

The other issues I will fix (hopefully this evening)
There is another issue I found by myself. Playing video with webkit in Preview is only visible after display setup :-(

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

855 is an import statement and should not be the last line of the file.

Need to look a 2407 but that should be over the weekend.

Preview display is in the top left corner of the previw area and not in the middle.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Line 862 is an import statement which should be a the top of the fine not the last line.

Line 716 isWebLoaded should be deleted as it is not referenced due to other moves.
1190 Indents

Slidecontroller you need to make a new group for next and previous buttons so you can use
self.toolbar.makeWidgetsVisible(self.loopList).

2440 to 2661 cannot be tested so should not go in.

review: Needs Fixing
Revision history for this message
John Cegalis (jseagull1) wrote : Posted in a previous version of this proposal

I'm not sure if you are looking for input or errors but I was testing your branch and the text in front of video worked great. I unchecked webkit and left phonon enabled and it did not work. I then unchecked phonon and it came crashing down and now OpenLP will not start. Here is the debug link I get. I didn't want to paste the whole mess in here.
Great work.
http://pastebin.com/iL0kentD

Revision history for this message
John Cegalis (jseagull1) wrote : Posted in a previous version of this proposal

As a followup, I cleaned up the files showing in the media config and it started up again.
I removed the media showing and kept this.
status=1
override%20player=0
All is well and thank you for working on this.

Revision history for this message
John Cegalis (jseagull1) wrote : Posted in a previous version of this proposal

Kubuntu Oneiric
Sorry for the trouble but I continued testing and found out what is happening.

I unchecked webkit and phonon as I said above and get this traceback below.
Looking at the media heading in the config file it adds "players=" as you see at the bottom. That addition to the config stops OpenLP from opening. If I remove "players=" from the config file it starts right up.

Traceback (most recent call last):
  File "/home/john/Projects/media_rewrite/openlp/core/ui/media/mediacontroller.py", line 92, in set_active_players
    self.mediaPlayers[player].isActive = True
KeyError: u''

[media]
status=1
override%20player=0
last%20directory=/home/john/Desktop/GCOH Web/video
media%20count=2
media%200=/home/john/Desktop/GCOH Web/video/Timelapse_Video_Sequence_-_ReelWorship.mp4
media%201=/home/john/Desktop/GCOH Web/video/flag.avi
players=

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

See John's comment.

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Print statement left in
VLC definition added to build and not defined
Code which does not run still included

Horrible flash when a video goes live. Send live a video player starts on the control screen disappears and reappears on the display screen. This did not happen in the last version.

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> Line 716 isWebLoaded should be deleted as it is not referenced due to other moves.
is neede because of is used for Display and Maindisplay

> 1190 Indents
done

> Slidecontroller you need to make a new group for next and previous buttons so you can use
> self.toolbar.makeWidgetsVisible(self.loopList).
done

> 2440 to 2661 cannot be tested so should not go in.
This code is active for loading a new media item. (see mediaitem.py line 199)

> VLC statement was wrong

> horrible flash I have to analysed

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

isWebLoaded is defined twice now in both display and maindisplay

previewDisplay is not used in mediaitem.

review: Needs Fixing
Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> isWebLoaded is defined twice now in both display and maindisplay
Your right. I will remove the second one in maindisplay

> previewDisplay is not used in mediaitem.
However it will be used implicitely. (line 2576)
For using the video player mechanism it is needed to have a controller (which deliver the related control functions) and a related display class (which contain all available player widgets).
In this case while loading a new media item with the open dialog the choosen video file will be checked. Therefore the media plugin has an own mediacontroller (consist of a controller and an invisible preview Display).

Unfortunately I cant reproduce your "Horrible flash". So Im not sure what I can do!

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

The horrible flash only started yesterday so what did you break /fix !

Revision history for this message
John Cegalis (jseagull1) wrote : Posted in a previous version of this proposal

My crash problem is solved and flv files are playing fine on Kubuntu (if that is what you mean by horrible flash Tim).

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

No my horrible flash is a new window(may be the display) being created about 1/4 screen size. The video stating and the display moving to the final display and becoming large.
As this takes 0.5 of a sec all you see is a flash but enough to see something horrid.

Revision history for this message
John Cegalis (jseagull1) wrote : Posted in a previous version of this proposal

Maybe I need dual monitors. I'm not seeing it in the single display.

Revision history for this message
rimach (crichter) wrote : Posted in a previous version of this proposal

> No my horrible flash is a new window(may be the display) being created about
> 1/4 screen size. The video stating and the display moving to the final
> display and becoming large.
> As this takes 0.5 of a sec all you see is a flash but enough to see something
> horrid.
Hmm, please could you give me some more details?
- single or dual monitor mode
- starting video from mediamanager/service/preview
- with all types of player? (activate the override player option in media settings and choose an explicit mediaplayer)
- is this little new window a floating window and were it appears
- does this issue occour every time a video is started or only under some circumstances (after first start, after using a song, ...)

(What I found is that for used phonon player the theme background appears for a short time)

Revision history for this message
Tim Bentley (trb143) wrote :

Rats looks like I can approve this ;-)

One small bug which should not stop it going in is when a video is started behind text the sound is not muted.

review: Approve
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Looks and works for me too. Approved then it is!

review: Approve
Revision history for this message
rimach (crichter) wrote :

jippi :-)

Revision history for this message
Meinert Jordan (m2j) wrote :

Glückwunsch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/lib/htmlbuilder.py'
2--- openlp/core/lib/htmlbuilder.py 2011-11-24 21:25:38 +0000
3+++ openlp/core/lib/htmlbuilder.py 2011-12-01 18:30:31 +0000
4@@ -53,8 +53,8 @@
5 position: absolute;
6 left: 0px;
7 top: 0px;
8- width: %spx;
9- height: %spx;
10+ width: 100%%;
11+ height: 100%%;
12 }
13 #black {
14 z-index: 8;
15@@ -67,12 +67,6 @@
16 #image {
17 z-index: 2;
18 }
19-#video1 {
20- z-index: 3;
21-}
22-#video2 {
23- z-index: 3;
24-}
25 %s
26 #footer {
27 position: absolute;
28@@ -90,90 +84,9 @@
29 </style>
30 <script>
31 var timer = null;
32- var video_timer = null;
33- var current_video = '1';
34 var transition = %s;
35-
36- function show_video(state, path, volume, loop){
37- // Note, the preferred method for looping would be to use the
38- // video tag loop attribute.
39- // But QtWebKit doesn't support this. Neither does it support the
40- // onended event, hence the setInterval()
41- // In addition, setting the currentTime attribute to zero to restart
42- // the video raises an INDEX_SIZE_ERROR: DOM Exception 1
43- // To complicate it further, sometimes vid.currentTime stops
44- // slightly short of vid.duration and vid.ended is intermittent!
45- //
46- // Note, currently the background may go black between loops. Not
47- // desirable. Need to investigate using two <video>'s, and hiding/
48- // preloading one, and toggle between the two when looping.
49-
50- if(current_video=='1'){
51- var vid = document.getElementById('video1');
52- var vid2 = document.getElementById('video2');
53- } else {
54- var vid = document.getElementById('video2');
55- var vid2 = document.getElementById('video1');
56- }
57- if(volume != null){
58- vid.volume = volume;
59- vid2.volume = volume;
60- }
61- switch(state){
62- case 'init':
63- vid.src = 'file:///' + path;
64- vid2.src = 'file:///' + path;
65- if(loop == null) loop = false;
66- vid.looping = loop;
67- vid2.looping = loop;
68- vid.load();
69- break;
70- case 'load':
71- vid2.style.visibility = 'hidden';
72- vid2.load();
73- break;
74- case 'play':
75- vid.play();
76- vid.style.visibility = 'visible';
77- if(vid.looping){
78- video_timer = setInterval(
79- function() {
80- show_video('poll');
81- }, 200);
82- }
83- break;
84- case 'pause':
85- if(video_timer!=null){
86- clearInterval(video_timer);
87- video_timer = null;
88- }
89- vid.pause();
90- break;
91- case 'stop':
92- show_video('pause');
93- vid.style.visibility = 'hidden';
94- break;
95- case 'poll':
96- if(vid.ended||vid.currentTime+0.2>vid.duration)
97- show_video('swap');
98- break;
99- case 'swap':
100- show_video('pause');
101- if(current_video=='1')
102- current_video = '2';
103- else
104- current_video = '1';
105- show_video('play');
106- show_video('load');
107- break;
108- case 'close':
109- show_video('stop');
110- vid.src = '';
111- vid2.src = '';
112- break;
113- }
114- }
115 %s
116+
117 function show_image(src){
118 var img = document.getElementById('image');
119 img.src = src;
120@@ -186,18 +99,14 @@
121 function show_blank(state){
122 var black = 'none';
123 var lyrics = '';
124- var pause = false;
125 switch(state){
126 case 'theme':
127 lyrics = 'hidden';
128- pause = true;
129 break;
130 case 'black':
131 black = 'block';
132- pause = true;
133 break;
134 case 'desktop':
135- pause = true;
136 break;
137 }
138 document.getElementById('black').style.display = black;
139@@ -210,13 +119,6 @@
140 if(shadow!=null)
141 shadow.style.visibility = lyrics;
142 document.getElementById('footer').style.visibility = lyrics;
143- var vid = document.getElementById('video');
144- if(vid.src != ''){
145- if(pause)
146- vid.pause();
147- else
148- vid.play();
149- }
150 }
151
152 function show_footer(footertext){
153@@ -277,10 +179,6 @@
154 <body>
155 <img id="bgimage" class="size" %s />
156 <img id="image" class="size" %s />
157-<video id="video1" class="size" style="visibility:hidden" autobuffer preload>
158-</video>
159-<video id="video2" class="size" style="visibility:hidden" autobuffer preload>
160-</video>
161 %s
162 %s
163 <div id="footer" class="footer"></div>
164@@ -336,7 +234,6 @@
165 js_additions += plugin.getDisplayJavaScript()
166 html_additions += plugin.getDisplayHtml()
167 html = HTMLSRC % (build_background_css(item, width, height),
168- width, height,
169 css_additions,
170 build_footer_css(item, height),
171 build_lyrics_css(item, webkitvers),
172@@ -609,4 +506,3 @@
173 item.footer.width(), theme.font_footer_name,
174 theme.font_footer_size, theme.font_footer_color)
175 return lyrics_html
176-
177
178=== added file 'openlp/core/lib/mediaplayer.py'
179--- openlp/core/lib/mediaplayer.py 1970-01-01 00:00:00 +0000
180+++ openlp/core/lib/mediaplayer.py 2011-12-01 18:30:31 +0000
181@@ -0,0 +1,137 @@
182+# -*- coding: utf-8 -*-
183+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
184+
185+###############################################################################
186+# OpenLP - Open Source Lyrics Projection #
187+# --------------------------------------------------------------------------- #
188+# Copyright (c) 2008-2011 Raoul Snyman #
189+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
190+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
191+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
192+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
193+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
194+# --------------------------------------------------------------------------- #
195+# This program is free software; you can redistribute it and/or modify it #
196+# under the terms of the GNU General Public License as published by the Free #
197+# Software Foundation; version 2 of the License. #
198+# #
199+# This program is distributed in the hope that it will be useful, but WITHOUT #
200+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
201+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
202+# more details. #
203+# #
204+# You should have received a copy of the GNU General Public License along #
205+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
206+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
207+###############################################################################
208+
209+from openlp.core.ui.media import MediaState
210+
211+class MediaPlayer(object):
212+ """
213+ This is the base class media Player class to provide OpenLP with a pluggable media display
214+ framework.
215+ """
216+
217+ def __init__(self, parent, name=u'media_player'):
218+ self.parent = parent
219+ self.name = name
220+ self.available = self.check_available()
221+ self.isActive = False
222+ self.canBackground = False
223+ self.canFolder = False
224+ self.state = MediaState.Off
225+ self.hasOwnWidget = False
226+ self.audio_extensions_list = []
227+ self.video_extensions_list = []
228+
229+ def check_available(self):
230+ """
231+ Player is available on this machine
232+ """
233+ return False
234+
235+ def setup(self, display):
236+ """
237+ Create the related widgets for the current display
238+ """
239+ pass
240+
241+ def load(self, display):
242+ """
243+ Load a new media file and check if it is valid
244+ """
245+ return True
246+
247+ def resize(self, display):
248+ """
249+ If the main display size or position is changed, the media widgets
250+ should also resized
251+ """
252+ pass
253+
254+ def play(self, display):
255+ """
256+ Starts playing of current Media File
257+ """
258+ pass
259+
260+ def pause(self, display):
261+ """
262+ Pause of current Media File
263+ """
264+ pass
265+
266+ def stop(self, display):
267+ """
268+ Stop playing of current Media File
269+ """
270+ pass
271+
272+ def volume(self, display, vol):
273+ """
274+ Change volume of current Media File
275+ """
276+ pass
277+
278+ def seek(self, display, seekVal):
279+ """
280+ Change playing position of current Media File
281+ """
282+ pass
283+
284+ def reset(self, display):
285+ """
286+ Remove the current loaded video
287+ """
288+ pass
289+
290+ def set_visible(self, display, status):
291+ """
292+ Show/Hide the media widgets
293+ """
294+ pass
295+
296+ def update_ui(self, display):
297+ """
298+ Do some ui related stuff (e.g. update the seek slider)
299+ """
300+ pass
301+
302+ def get_media_display_css(self):
303+ """
304+ Add css style sheets to htmlbuilder
305+ """
306+ return u''
307+
308+ def get_media_display_javascript(self):
309+ """
310+ Add javascript functions to htmlbuilder
311+ """
312+ return u''
313+
314+ def get_media_display_html(self):
315+ """
316+ Add html code to htmlbuilder
317+ """
318+ return u''
319
320=== modified file 'openlp/core/lib/plugin.py'
321--- openlp/core/lib/plugin.py 2011-10-17 18:01:07 +0000
322+++ openlp/core/lib/plugin.py 2011-12-01 18:30:31 +0000
323@@ -168,6 +168,7 @@
324 self.mediadock = plugin_helpers[u'toolbox']
325 self.pluginManager = plugin_helpers[u'pluginmanager']
326 self.formparent = plugin_helpers[u'formparent']
327+ self.mediaController = plugin_helpers[u'mediacontroller']
328 QtCore.QObject.connect(Receiver.get_receiver(),
329 QtCore.SIGNAL(u'%s_add_service_item' % self.name),
330 self.processAddServiceEvent)
331@@ -395,4 +396,3 @@
332 Add html code to htmlbuilder.
333 """
334 return u''
335-
336
337=== modified file 'openlp/core/lib/renderer.py'
338--- openlp/core/lib/renderer.py 2011-11-02 20:09:06 +0000
339+++ openlp/core/lib/renderer.py 2011-12-01 18:30:31 +0000
340@@ -76,7 +76,7 @@
341 self.theme_data = None
342 self.bg_frame = None
343 self.force_page = False
344- self.display = MainDisplay(None, self.imageManager, False)
345+ self.display = MainDisplay(None, self.imageManager, False, self)
346 self.display.setup()
347
348 def update_display(self):
349@@ -87,7 +87,7 @@
350 self._calculate_default()
351 if self.display:
352 self.display.close()
353- self.display = MainDisplay(None, self.imageManager, False)
354+ self.display = MainDisplay(None, self.imageManager, False, self)
355 self.display.setup()
356 self.bg_frame = None
357 self.theme_data = None
358
359=== modified file 'openlp/core/ui/__init__.py'
360--- openlp/core/ui/__init__.py 2011-10-29 19:13:11 +0000
361+++ openlp/core/ui/__init__.py 2011-12-01 18:30:31 +0000
362@@ -77,10 +77,10 @@
363 from filerenameform import FileRenameForm
364 from starttimeform import StartTimeForm
365 from screen import ScreenList
366-from maindisplay import MainDisplay
367+from maindisplay import MainDisplay, Display
368 from servicenoteform import ServiceNoteForm
369 from serviceitemeditform import ServiceItemEditForm
370-from slidecontroller import SlideController
371+from slidecontroller import SlideController, Controller
372 from splashscreen import SplashScreen
373 from generaltab import GeneralTab
374 from themestab import ThemesTab
375
376=== modified file 'openlp/core/ui/maindisplay.py'
377--- openlp/core/ui/maindisplay.py 2011-11-01 06:09:21 +0000
378+++ openlp/core/ui/maindisplay.py 2011-12-01 18:30:31 +0000
379@@ -31,7 +31,7 @@
380 import logging
381 import os
382
383-from PyQt4 import QtCore, QtGui, QtWebKit
384+from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
385 from PyQt4.phonon import Phonon
386
387 from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \
388@@ -44,11 +44,13 @@
389 #http://www.steveheffernan.com/html5-video-player/demo-video-player.html
390 #http://html5demos.com/two-videos
391
392-class MainDisplay(QtGui.QGraphicsView):
393- """
394- This is the display screen.
395- """
396- def __init__(self, parent, imageManager, live):
397+class Display(QtGui.QGraphicsView):
398+ """
399+ This is a general display screen class. Here the general display settings
400+ will done. It will be used as specialized classes by Main Display and
401+ Preview display.
402+ """
403+ def __init__(self, parent, live, controller):
404 if live:
405 QtGui.QGraphicsView.__init__(self)
406 # Overwrite the parent() method.
407@@ -56,12 +58,60 @@
408 else:
409 QtGui.QGraphicsView.__init__(self, parent)
410 self.isLive = live
411+ self.controller = controller
412+ self.screen = {}
413+ self.plugins = PluginManager.get_instance().plugins
414+ self.setViewport(QtOpenGL.QGLWidget())
415+
416+ def setup(self):
417+ """
418+ Set up and build the screen base
419+ """
420+ log.debug(u'Start Display base setup (live = %s)' % self.isLive)
421+ self.setGeometry(self.screen[u'size'])
422+ log.debug(u'Setup webView')
423+ self.webView = QtWebKit.QWebView(self)
424+ self.webView.setGeometry(0, 0,
425+ self.screen[u'size'].width(), self.screen[u'size'].height())
426+ self.webView.settings().setAttribute(
427+ QtWebKit.QWebSettings.PluginsEnabled, True)
428+ self.page = self.webView.page()
429+ self.frame = self.page.mainFrame()
430+ if self.isLive and log.getEffectiveLevel() == logging.DEBUG:
431+ self.webView.settings().setAttribute(
432+ QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)
433+ QtCore.QObject.connect(self.webView,
434+ QtCore.SIGNAL(u'loadFinished(bool)'), self.isWebLoaded)
435+ self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
436+ self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
437+ self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
438+ QtCore.Qt.ScrollBarAlwaysOff)
439+ self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
440+ QtCore.Qt.ScrollBarAlwaysOff)
441+
442+ def resizeEvent(self, ev):
443+ self.webView.setGeometry(0, 0,
444+ self.width(), self.height())
445+
446+ def isWebLoaded(self):
447+ """
448+ Called by webView event to show display is fully loaded
449+ """
450+ log.debug(u'Webloaded')
451+ self.webLoaded = True
452+
453+
454+class MainDisplay(Display):
455+ """
456+ This is the display screen as a specialized class from the Display class
457+ """
458+ def __init__(self, parent, imageManager, live, controller):
459+ Display.__init__(self, parent, live, controller)
460 self.imageManager = imageManager
461 self.screens = ScreenList.get_instance()
462 self.plugins = PluginManager.get_instance().plugins
463 self.rebuildCSS = False
464 self.hideMode = None
465- self.videoHide = False
466 self.override = {}
467 self.retranslateUi()
468 self.mediaObject = None
469@@ -81,9 +131,6 @@
470 QtCore.QObject.connect(Receiver.get_receiver(),
471 QtCore.SIGNAL(u'live_display_show'), self.showDisplay)
472 QtCore.QObject.connect(Receiver.get_receiver(),
473- QtCore.SIGNAL(u'openlp_phonon_creation'),
474- self.createMediaObject)
475- QtCore.QObject.connect(Receiver.get_receiver(),
476 QtCore.SIGNAL(u'update_display_css'), self.cssChanged)
477 QtCore.QObject.connect(Receiver.get_receiver(),
478 QtCore.SIGNAL(u'config_updated'), self.configChanged)
479@@ -115,36 +162,9 @@
480 Set up and build the output screen
481 """
482 log.debug(u'Start MainDisplay setup (live = %s)' % self.isLive)
483- self.usePhonon = QtCore.QSettings().value(
484- u'media/use phonon', QtCore.QVariant(True)).toBool()
485- self.phononActive = False
486 self.screen = self.screens.current
487 self.setVisible(False)
488- self.setGeometry(self.screen[u'size'])
489- self.videoWidget = Phonon.VideoWidget(self)
490- self.videoWidget.setVisible(False)
491- self.videoWidget.setGeometry(QtCore.QRect(0, 0,
492- self.screen[u'size'].width(), self.screen[u'size'].height()))
493- if self.isLive:
494- if not self.firstTime:
495- self.createMediaObject()
496- log.debug(u'Setup webView')
497- self.webView = QtWebKit.QWebView(self)
498- self.webView.setGeometry(0, 0,
499- self.screen[u'size'].width(), self.screen[u'size'].height())
500- self.page = self.webView.page()
501- self.frame = self.page.mainFrame()
502- if self.isLive and log.getEffectiveLevel() == logging.DEBUG:
503- self.webView.settings().setAttribute(
504- QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)
505- QtCore.QObject.connect(self.webView,
506- QtCore.SIGNAL(u'loadFinished(bool)'), self.isWebLoaded)
507- self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
508- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
509- self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
510- QtCore.Qt.ScrollBarAlwaysOff)
511- self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
512- QtCore.Qt.ScrollBarAlwaysOff)
513+ Display.setup(self)
514 if self.isLive:
515 # Build the initial frame.
516 image_file = QtCore.QSettings().value(u'advanced/default image',
517@@ -180,24 +200,6 @@
518 self.primary = True
519 log.debug(u'Finished MainDisplay setup')
520
521- def createMediaObject(self):
522- self.firstTime = False
523- log.debug(u'Creating Phonon objects - Start for %s', self.isLive)
524- self.mediaObject = Phonon.MediaObject(self)
525- self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject)
526- Phonon.createPath(self.mediaObject, self.videoWidget)
527- Phonon.createPath(self.mediaObject, self.audio)
528- QtCore.QObject.connect(self.mediaObject,
529- QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'),
530- self.videoState)
531- QtCore.QObject.connect(self.mediaObject,
532- QtCore.SIGNAL(u'finished()'),
533- self.videoFinished)
534- QtCore.QObject.connect(self.mediaObject,
535- QtCore.SIGNAL(u'tick(qint64)'),
536- self.videoTick)
537- log.debug(u'Creating Phonon objects - Finished for %s', self.isLive)
538-
539 def text(self, slide):
540 """
541 Add the slide text from slideController
542@@ -221,8 +223,8 @@
543 The text to be displayed.
544 """
545 log.debug(u'alert to display')
546- if self.height() != self.screen[u'size'].height() or not \
547- self.isVisible() or self.videoWidget.isVisible():
548+ if self.height() != self.screen[u'size'].height() or \
549+ not self.isVisible():
550 shrink = True
551 js = u'show_alert("%s", "%s")' % (
552 text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'),
553@@ -233,22 +235,18 @@
554 text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"'))
555 height = self.frame.evaluateJavaScript(js)
556 if shrink:
557- if self.phononActive:
558- shrinkItem = self.webView
559- else:
560- shrinkItem = self
561 if text:
562 alert_height = int(height.toString())
563- shrinkItem.resize(self.width(), alert_height)
564- shrinkItem.setVisible(True)
565+ self.resize(self.width(), alert_height)
566+ self.setVisible(True)
567 if location == AlertLocation.Middle:
568- shrinkItem.move(self.screen[u'size'].left(),
569+ self.move(self.screen[u'size'].left(),
570 (self.screen[u'size'].height() - alert_height) / 2)
571 elif location == AlertLocation.Bottom:
572- shrinkItem.move(self.screen[u'size'].left(),
573+ self.move(self.screen[u'size'].left(),
574 self.screen[u'size'].height() - alert_height)
575 else:
576- shrinkItem.setVisible(False)
577+ self.setVisible(False)
578 self.setGeometry(self.screen[u'size'])
579
580 def directImage(self, name, path, background):
581@@ -276,7 +274,7 @@
582 """
583 log.debug(u'image to display')
584 image = self.imageManager.get_image_bytes(name)
585- self.resetVideo()
586+ self.controller.mediaController.video_reset(self.controller)
587 self.displayImage(image)
588
589 def displayImage(self, image):
590@@ -303,135 +301,6 @@
591 # clear the cache
592 self.override = {}
593
594- def resetVideo(self):
595- """
596- Used after Video plugin has changed the background
597- """
598- log.debug(u'resetVideo')
599- if self.phononActive:
600- self.mediaObject.stop()
601- self.mediaObject.clearQueue()
602- self.webView.setVisible(True)
603- self.videoWidget.setVisible(False)
604- self.phononActive = False
605- else:
606- self.frame.evaluateJavaScript(u'show_video("close");')
607- self.override = {}
608-
609- def videoPlay(self):
610- """
611- Responds to the request to play a loaded video
612- """
613- log.debug(u'videoPlay')
614- if self.phononActive:
615- self.mediaObject.play()
616- else:
617- self.frame.evaluateJavaScript(u'show_video("play");')
618- # show screen
619- if self.isLive:
620- self.setVisible(True)
621-
622- def videoPause(self):
623- """
624- Responds to the request to pause a loaded video
625- """
626- log.debug(u'videoPause')
627- if self.phononActive:
628- self.mediaObject.pause()
629- else:
630- self.frame.evaluateJavaScript(u'show_video("pause");')
631-
632- def videoStop(self):
633- """
634- Responds to the request to stop a loaded video
635- """
636- log.debug(u'videoStop')
637- if self.phononActive:
638- self.mediaObject.stop()
639- else:
640- self.frame.evaluateJavaScript(u'show_video("stop");')
641-
642- def videoVolume(self, volume):
643- """
644- Changes the volume of a running video
645- """
646- log.debug(u'videoVolume %d' % volume)
647- vol = float(volume) / float(10)
648- if self.phononActive:
649- self.audio.setVolume(vol)
650- else:
651- self.frame.evaluateJavaScript(u'show_video(null, null, %s);' %
652- str(vol))
653-
654- def video(self, videoPath, volume, isBackground=False):
655- """
656- Loads and starts a video to run with the option of sound
657- """
658- # We request a background video but have no service Item
659- if isBackground and not hasattr(self, u'serviceItem'):
660- return False
661- if not self.mediaObject:
662- self.createMediaObject()
663- log.debug(u'video')
664- self.webLoaded = True
665- self.setGeometry(self.screen[u'size'])
666- # We are running a background theme
667- self.override[u'theme'] = u''
668- self.override[u'video'] = True
669- vol = float(volume) / float(10)
670- if isBackground or not self.usePhonon:
671- js = u'show_video("init", "%s", %s, true); show_video("play");' % \
672- (videoPath.replace(u'\\', u'\\\\'), str(vol))
673- self.frame.evaluateJavaScript(js)
674- else:
675- self.phononActive = True
676- self.mediaObject.stop()
677- self.mediaObject.clearQueue()
678- self.mediaObject.setCurrentSource(Phonon.MediaSource(videoPath))
679- # Need the timer to trigger set the trigger to 200ms
680- # Value taken from web documentation.
681- if self.serviceItem.end_time != 0:
682- self.mediaObject.setTickInterval(200)
683- self.mediaObject.play()
684- self.webView.setVisible(False)
685- self.videoWidget.setVisible(True)
686- self.audio.setVolume(vol)
687- return True
688-
689- def videoState(self, newState, oldState):
690- """
691- Start the video at a predetermined point.
692- """
693- if newState == Phonon.PlayingState \
694- and oldState != Phonon.PausedState \
695- and self.serviceItem.start_time > 0:
696- # set start time in milliseconds
697- self.mediaObject.seek(self.serviceItem.start_time * 1000)
698-
699- def videoFinished(self):
700- """
701- Blank the Video when it has finished so the final frame is not left
702- hanging
703- """
704- self.videoStop()
705- self.hideDisplay(HideMode.Blank)
706- self.phononActive = False
707- self.videoHide = True
708-
709- def videoTick(self, tick):
710- """
711- Triggered on video tick every 200 milli seconds
712- """
713- if tick > self.serviceItem.end_time * 1000:
714- self.videoFinished()
715-
716- def isWebLoaded(self):
717- """
718- Called by webView event to show display is fully loaded
719- """
720- log.debug(u'Webloaded')
721- self.webLoaded = True
722-
723 def preview(self):
724 """
725 Generates a preview of the image displayed.
726@@ -511,16 +380,12 @@
727 if serviceItem.foot_text:
728 self.footer(serviceItem.foot_text)
729 # if was hidden keep it hidden
730- if self.hideMode and self.isLive:
731+ if self.hideMode and self.isLive and not serviceItem.is_media():
732 if QtCore.QSettings().value(u'general/auto unblank',
733 QtCore.QVariant(False)).toBool():
734 Receiver.send_message(u'slidecontroller_live_unblank')
735 else:
736 self.hideDisplay(self.hideMode)
737- # display hidden for video end we have a new item so must be shown
738- if self.videoHide and self.isLive:
739- self.videoHide = False
740- self.showDisplay()
741 self.__hideMouse()
742
743 def footer(self, text):
744@@ -538,8 +403,6 @@
745 Store the images so they can be replaced when required
746 """
747 log.debug(u'hideDisplay mode = %d', mode)
748- if self.phononActive:
749- self.videoPause()
750 if mode == HideMode.Screen:
751 self.frame.evaluateJavaScript(u'show_blank("desktop");')
752 self.setVisible(False)
753@@ -550,7 +413,6 @@
754 if mode != HideMode.Screen:
755 if self.isHidden():
756 self.setVisible(True)
757- if self.phononActive:
758 self.webView.setVisible(True)
759 self.hideMode = mode
760
761@@ -564,9 +426,6 @@
762 self.frame.evaluateJavaScript('show_blank("show");')
763 if self.isHidden():
764 self.setVisible(True)
765- if self.phononActive:
766- self.webView.setVisible(False)
767- self.videoPlay()
768 self.hideMode = None
769 # Trigger actions when display is active again
770 if self.isLive:
771
772=== modified file 'openlp/core/ui/mainwindow.py'
773--- openlp/core/ui/mainwindow.py 2011-10-22 11:09:01 +0000
774+++ openlp/core/ui/mainwindow.py 2011-12-01 18:30:31 +0000
775@@ -42,6 +42,7 @@
776 from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \
777 ThemeManager, SlideController, PluginForm, MediaDockManager, \
778 ShortcutListForm, FormattingTagForm
779+from openlp.core.ui.media import MediaController
780 from openlp.core.utils import AppLocation, add_actions, LanguageManager, \
781 get_application_version, delete_file
782 from openlp.core.utils.actions import ActionList, CategoryOrder
783@@ -557,6 +558,7 @@
784 self.pluginManager = PluginManager(pluginpath)
785 self.pluginHelpers = {}
786 self.imageManager = ImageManager()
787+ self.mediaController = MediaController(self)
788 # Set up the interface
789 self.setupUi(self)
790 # Load settings after setupUi so default UI sizes are overwritten
791@@ -644,6 +646,7 @@
792 self.pluginHelpers[u'toolbox'] = self.mediaDockManager
793 self.pluginHelpers[u'pluginmanager'] = self.pluginManager
794 self.pluginHelpers[u'formparent'] = self
795+ self.pluginHelpers[u'mediacontroller'] = self.mediaController
796 self.pluginManager.find_plugins(pluginpath, self.pluginHelpers)
797 # hook methods have to happen after find_plugins. Find plugins needs
798 # the controllers hence the hooks have moved from setupUI() to here
799
800=== added directory 'openlp/core/ui/media'
801=== added file 'openlp/core/ui/media/__init__.py'
802--- openlp/core/ui/media/__init__.py 1970-01-01 00:00:00 +0000
803+++ openlp/core/ui/media/__init__.py 2011-12-01 18:30:31 +0000
804@@ -0,0 +1,65 @@
805+# -*- coding: utf-8 -*-
806+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
807+
808+###############################################################################
809+# OpenLP - Open Source Lyrics Projection #
810+# --------------------------------------------------------------------------- #
811+# Copyright (c) 2008-2011 Raoul Snyman #
812+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
813+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
814+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
815+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
816+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
817+# --------------------------------------------------------------------------- #
818+# This program is free software; you can redistribute it and/or modify it #
819+# under the terms of the GNU General Public License as published by the Free #
820+# Software Foundation; version 2 of the License. #
821+# #
822+# This program is distributed in the hope that it will be useful, but WITHOUT #
823+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
824+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
825+# more details. #
826+# #
827+# You should have received a copy of the GNU General Public License along #
828+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
829+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
830+###############################################################################
831+
832+class MediaState(object):
833+ """
834+ An enumeration for possible States of the Media Player (copied partially
835+ from Phonon::State)
836+ """
837+ Loading = 0
838+ Stopped = 1
839+ Playing = 2
840+ Paused = 4
841+ Off = 6
842+
843+
844+class MediaType(object):
845+ """
846+ An enumeration of possibible Media Types
847+ """
848+ Unused = 0
849+ Audio = 1
850+ Video = 2
851+ CD = 3
852+ DVD = 4
853+ Folder = 5
854+
855+
856+class MediaInfo(object):
857+ """
858+ This class hold the media related infos
859+ """
860+ file_info = None
861+ volume = 100
862+ is_flash = False
863+ is_background = False
864+ length = 0
865+ start_time = 0
866+ end_time = 0
867+ media_type = MediaType()
868+
869+from mediacontroller import MediaController
870
871=== added file 'openlp/core/ui/media/mediacontroller.py'
872--- openlp/core/ui/media/mediacontroller.py 1970-01-01 00:00:00 +0000
873+++ openlp/core/ui/media/mediacontroller.py 2011-12-01 18:30:31 +0000
874@@ -0,0 +1,576 @@
875+# -*- coding: utf-8 -*-
876+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
877+
878+###############################################################################
879+# OpenLP - Open Source Lyrics Projection #
880+# --------------------------------------------------------------------------- #
881+# Copyright (c) 2008-2011 Raoul Snyman #
882+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
883+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
884+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
885+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
886+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
887+# --------------------------------------------------------------------------- #
888+# This program is free software; you can redistribute it and/or modify it #
889+# under the terms of the GNU General Public License as published by the Free #
890+# Software Foundation; version 2 of the License. #
891+# #
892+# This program is distributed in the hope that it will be useful, but WITHOUT #
893+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
894+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
895+# more details. #
896+# #
897+# You should have received a copy of the GNU General Public License along #
898+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
899+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
900+###############################################################################
901+
902+import logging
903+
904+import sys, os,time
905+from PyQt4 import QtCore, QtGui, QtWebKit
906+
907+from openlp.core.lib import OpenLPToolbar, Receiver, translate
908+from openlp.core.lib.mediaplayer import MediaPlayer
909+from openlp.core.lib.ui import UiStrings, critical_error_message_box
910+from openlp.core.ui.media import MediaState, MediaInfo, MediaType
911+from openlp.core.utils import AppLocation
912+
913+log = logging.getLogger(__name__)
914+
915+class MediaController(object):
916+ """
917+ The implementation of the Media Controller. The Media Controller adds an own
918+ class for every Player. Currently these are QtWebkit, Phonon and planed Vlc.
919+ """
920+
921+ def __init__(self, parent):
922+ self.parent = parent
923+ self.mediaPlayers = {}
924+ self.controller = []
925+ self.overridenPlayer = ''
926+ self.curDisplayMediaPlayer = {}
927+ # Timer for video state
928+ self.timer = QtCore.QTimer()
929+ self.timer.setInterval(200)
930+ self.withLivePreview = False
931+ self.check_available_media_players()
932+ # Signals
933+ QtCore.QObject.connect(self.timer,
934+ QtCore.SIGNAL("timeout()"), self.video_state)
935+ QtCore.QObject.connect(Receiver.get_receiver(),
936+ QtCore.SIGNAL(u'media_playback_play'), self.video_play)
937+ QtCore.QObject.connect(Receiver.get_receiver(),
938+ QtCore.SIGNAL(u'media_playback_pause'), self.video_pause)
939+ QtCore.QObject.connect(Receiver.get_receiver(),
940+ QtCore.SIGNAL(u'media_playback_stop'), self.video_stop)
941+ QtCore.QObject.connect(Receiver.get_receiver(),
942+ QtCore.SIGNAL(u'seek_slider'), self.video_seek)
943+ QtCore.QObject.connect(Receiver.get_receiver(),
944+ QtCore.SIGNAL(u'volume_slider'), self.video_volume)
945+ QtCore.QObject.connect(Receiver.get_receiver(),
946+ QtCore.SIGNAL(u'media_hide'), self.video_hide)
947+ QtCore.QObject.connect(Receiver.get_receiver(),
948+ QtCore.SIGNAL(u'media_blank'), self.video_blank)
949+ QtCore.QObject.connect(Receiver.get_receiver(),
950+ QtCore.SIGNAL(u'media_unblank'), self.video_unblank)
951+ QtCore.QObject.connect(Receiver.get_receiver(),
952+ QtCore.SIGNAL(u'media_override_player'), self.override_player)
953+ # Signals for background video
954+ QtCore.QObject.connect(Receiver.get_receiver(),
955+ QtCore.SIGNAL(u'songs_hide'), self.video_hide)
956+ QtCore.QObject.connect(Receiver.get_receiver(),
957+ QtCore.SIGNAL(u'songs_unblank'), self.video_unblank)
958+ QtCore.QObject.connect(Receiver.get_receiver(),
959+ QtCore.SIGNAL(u'mediaitem_media_rebuild'), self.set_active_players)
960+
961+ def set_active_players(self):
962+ playerSettings = str(QtCore.QSettings().value(u'media/players',
963+ QtCore.QVariant(u'webkit')).toString())
964+ if len(playerSettings) == 0:
965+ playerSettings = u'webkit'
966+ savedPlayers = playerSettings.split(u',')
967+ for player in self.mediaPlayers.keys():
968+ if player in savedPlayers:
969+ self.mediaPlayers[player].isActive = True
970+ else:
971+ self.mediaPlayers[player].isActive = False
972+
973+ def register_controllers(self, controller):
974+ """
975+ Register each media Player controller (Webkit, Phonon, etc) and store
976+ for later use
977+ """
978+ if controller.check_available():
979+ self.mediaPlayers[controller.name] = controller
980+
981+ def check_available_media_players(self):
982+ """
983+ Check to see if we have any media Player's available. If Not do not
984+ install the plugin.
985+ """
986+ log.debug(u'check_available_media_players')
987+ controller_dir = os.path.join(
988+ AppLocation.get_directory(AppLocation.AppDir),
989+ u'core', u'ui', u'media')
990+ for filename in os.listdir(controller_dir):
991+ if filename.endswith(u'player.py') and \
992+ not filename == 'media_player.py':
993+ path = os.path.join(controller_dir, filename)
994+ if os.path.isfile(path):
995+ modulename = u'openlp.core.ui.media.' + \
996+ os.path.splitext(filename)[0]
997+ log.debug(u'Importing controller %s', modulename)
998+ try:
999+ __import__(modulename, globals(), locals(), [])
1000+ except ImportError:
1001+ log.warn(u'Failed to import %s on path %s',
1002+ modulename, path)
1003+ controller_classes = MediaPlayer.__subclasses__()
1004+ for controller_class in controller_classes:
1005+ controller = controller_class(self)
1006+ self.register_controllers(controller)
1007+ if self.mediaPlayers:
1008+ playerSettings = str(QtCore.QSettings().value(u'media/players',
1009+ QtCore.QVariant(u'webkit')).toString())
1010+ savedPlayers = playerSettings.split(u',')
1011+ invalidMediaPlayers = [mediaPlayer for mediaPlayer in savedPlayers \
1012+ if not mediaPlayer in self.mediaPlayers]
1013+ if len(invalidMediaPlayers)>0:
1014+ [savedPlayers.remove(invalidPlayer) for invalidPlayer in invalidMediaPlayers]
1015+ newPlayerSetting = u','.join(savedPlayers)
1016+ QtCore.QSettings().setValue(u'media/players',
1017+ QtCore.QVariant(newPlayerSetting))
1018+ self.set_active_players()
1019+ return True
1020+ else:
1021+ return False
1022+
1023+ def video_state(self):
1024+ """
1025+ Check if there is a running media Player and do updating stuff (e.g.
1026+ update the UI)
1027+ """
1028+ if len(self.curDisplayMediaPlayer.keys()) == 0:
1029+ self.timer.stop()
1030+ else:
1031+ for display in self.curDisplayMediaPlayer.keys():
1032+ self.curDisplayMediaPlayer[display].resize(display)
1033+ self.curDisplayMediaPlayer[display].update_ui(display)
1034+ if self.curDisplayMediaPlayer[display] \
1035+ .state == MediaState.Playing:
1036+ return
1037+ self.timer.stop()
1038+
1039+ def get_media_display_css(self):
1040+ """
1041+ Add css style sheets to htmlbuilder
1042+ """
1043+ css = u''
1044+ for player in self.mediaPlayers.values():
1045+ if player.isActive:
1046+ css += player.get_media_display_css()
1047+ return css
1048+
1049+ def get_media_display_javascript(self):
1050+ """
1051+ Add javascript functions to htmlbuilder
1052+ """
1053+ js = u''
1054+ for player in self.mediaPlayers.values():
1055+ if player.isActive:
1056+ js += player.get_media_display_javascript()
1057+ return js
1058+
1059+ def get_media_display_html(self):
1060+ """
1061+ Add html code to htmlbuilder
1062+ """
1063+ html = u''
1064+ for player in self.mediaPlayers.values():
1065+ if player.isActive:
1066+ html += player.get_media_display_html()
1067+ return html
1068+
1069+ def add_controller_items(self, controller, control_panel):
1070+ self.controller.append(controller)
1071+ self.setup_generic_controls(controller, control_panel)
1072+ self.setup_special_controls(controller, control_panel)
1073+
1074+ def setup_generic_controls(self, controller, control_panel):
1075+ """
1076+ Add generic media control items (valid for all types of medias)
1077+ """
1078+ controller.media_info = MediaInfo()
1079+ # Build a Media ToolBar
1080+ controller.mediabar = OpenLPToolbar(controller)
1081+ controller.mediabar.addToolbarButton(
1082+ u'media_playback_play', u':/slides/media_playback_start.png',
1083+ translate('OpenLP.SlideController', 'Start playing media'),
1084+ controller.sendToPlugins)
1085+ controller.mediabar.addToolbarButton(
1086+ u'media_playback_pause', u':/slides/media_playback_pause.png',
1087+ translate('OpenLP.SlideController', 'Pause playing media'),
1088+ controller.sendToPlugins)
1089+ controller.mediabar.addToolbarButton(
1090+ u'media_playback_stop', u':/slides/media_playback_stop.png',
1091+ translate('OpenLP.SlideController', 'Stop playing media'),
1092+ controller.sendToPlugins)
1093+ # Build the seekSlider.
1094+ controller.seekSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
1095+ controller.seekSlider.setMaximum(1000)
1096+ controller.seekSlider.setToolTip(translate(
1097+ 'OpenLP.SlideController', 'Video position.'))
1098+ controller.seekSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
1099+ controller.seekSlider.setObjectName(u'seek_slider')
1100+ controller.mediabar.addToolbarWidget(u'Seek Slider',
1101+ controller.seekSlider)
1102+ # Build the volumeSlider.
1103+ controller.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
1104+ controller.volumeSlider.setTickInterval(10)
1105+ controller.volumeSlider.setTickPosition(QtGui.QSlider.TicksAbove)
1106+ controller.volumeSlider.setMinimum(0)
1107+ controller.volumeSlider.setMaximum(100)
1108+ controller.volumeSlider.setToolTip(translate(
1109+ 'OpenLP.SlideController', 'Audio Volume.'))
1110+ controller.volumeSlider.setValue(controller.media_info.volume)
1111+ controller.volumeSlider.setGeometry(QtCore.QRect(90, 160, 221, 24))
1112+ controller.volumeSlider.setObjectName(u'volume_slider')
1113+ controller.mediabar.addToolbarWidget(u'Audio Volume',
1114+ controller.volumeSlider)
1115+ control_panel.addWidget(controller.mediabar)
1116+ controller.mediabar.setVisible(False)
1117+ # Signals
1118+ QtCore.QObject.connect(controller.seekSlider,
1119+ QtCore.SIGNAL(u'sliderMoved(int)'), controller.sendToPlugins)
1120+ QtCore.QObject.connect(controller.volumeSlider,
1121+ QtCore.SIGNAL(u'sliderMoved(int)'), controller.sendToPlugins)
1122+
1123+ def setup_special_controls(self, controller, control_panel):
1124+ """
1125+ Special media Toolbars will be created here (e.g. for DVD Playback)
1126+ """
1127+ controller.media_info = MediaInfo()
1128+ # TODO: add Toolbar for DVD, ...
1129+
1130+ def setup_display(self, display):
1131+ """
1132+ After a new display is configured, all media related widget will be
1133+ created too
1134+ """
1135+ # clean up possible running old media files
1136+ self.finalise()
1137+ # update player status
1138+ self.set_active_players()
1139+ display.hasAudio = True
1140+ if not self.withLivePreview and \
1141+ display == self.parent.liveController.previewDisplay:
1142+ return
1143+ if display == self.parent.previewController.previewDisplay or \
1144+ display == self.parent.liveController.previewDisplay:
1145+ display.hasAudio = False
1146+ for player in self.mediaPlayers.values():
1147+ if player.isActive:
1148+ player.setup(display)
1149+
1150+ def set_controls_visible(self, controller, value):
1151+ # Generic controls
1152+ controller.mediabar.setVisible(value)
1153+ # Special controls: Here media type specific Controls will be enabled
1154+ # (e.g. for DVD control, ...)
1155+ # TODO
1156+
1157+ def resize(self, controller, display, player):
1158+ """
1159+ After Mainwindow changes or Splitter moved all related media widgets
1160+ have to be resized
1161+ """
1162+ player.resize(display)
1163+
1164+ def video(self, controller, file, muted, isBackground):
1165+ """
1166+ Loads and starts a video to run with the option of sound
1167+ """
1168+ log.debug(u'video')
1169+ isValid = False
1170+ # stop running videos
1171+ self.video_reset(controller)
1172+ controller.media_info = MediaInfo()
1173+ if muted:
1174+ controller.media_info.volume = 0
1175+ else:
1176+ controller.media_info.volume = controller.volumeSlider.value()
1177+ controller.media_info.file_info = QtCore.QFileInfo(file)
1178+ controller.media_info.is_background = isBackground
1179+ display = None
1180+ if controller.isLive:
1181+ if self.withLivePreview and controller.previewDisplay:
1182+ display = controller.previewDisplay
1183+ isValid = self.check_file_type(controller, display)
1184+ display = controller.display
1185+ isValid = self.check_file_type(controller, display)
1186+ display.override[u'theme'] = u''
1187+ display.override[u'video'] = True
1188+ controller.media_info.start_time = display.serviceItem.start_time
1189+ controller.media_info.end_time = display.serviceItem.end_time
1190+ elif controller.previewDisplay:
1191+ display = controller.previewDisplay
1192+ isValid = self.check_file_type(controller, display)
1193+ if not isValid:
1194+ # Media could not be loaded correctly
1195+ critical_error_message_box(
1196+ translate('MediaPlugin.MediaItem', 'Unsupported File'),
1197+ unicode(translate('MediaPlugin.MediaItem',
1198+ 'Unsupported File')))
1199+ return False
1200+ # dont care about actual theme, set a black background
1201+ if controller.isLive and ( \
1202+ controller.media_info.is_background == False):
1203+ display.frame.evaluateJavaScript(u'show_video( \
1204+ "setBackBoard", null, null, null,"visible");')
1205+ # now start playing
1206+ if controller.isLive and \
1207+ (QtCore.QSettings().value(u'general/auto unblank',
1208+ QtCore.QVariant(False)).toBool() or \
1209+ controller.media_info.is_background == True) or \
1210+ controller.isLive == False:
1211+ if not self.video_play([controller]):
1212+ critical_error_message_box(
1213+ translate('MediaPlugin.MediaItem', 'Unsupported File'),
1214+ unicode(translate('MediaPlugin.MediaItem',
1215+ 'Unsupported File')))
1216+ return False
1217+ self.set_controls_visible(controller, True)
1218+ log.debug(u'use %s controller' % self.curDisplayMediaPlayer[display])
1219+ return True
1220+
1221+ def check_file_type(self, controller, display):
1222+ """
1223+ Used to choose the right media Player type from the prioritized Player list
1224+ """
1225+ playerSettings = str(QtCore.QSettings().value(u'media/players',
1226+ QtCore.QVariant(u'webkit')).toString())
1227+ usedPlayers = playerSettings.split(u',')
1228+ if QtCore.QSettings().value(u'media/override player',
1229+ QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked:
1230+ if self.overridenPlayer != '':
1231+ usedPlayers = [self.overridenPlayer]
1232+ if controller.media_info.file_info.isFile():
1233+ suffix = u'*.%s' % controller.media_info.file_info.suffix().toLower()
1234+ for title in usedPlayers:
1235+ player = self.mediaPlayers[title]
1236+ if suffix in player.video_extensions_list:
1237+ if not controller.media_info.is_background or \
1238+ controller.media_info.is_background and player.canBackground:
1239+ self.resize(controller, display, player)
1240+ if player.load(display):
1241+ self.curDisplayMediaPlayer[display] = player
1242+ controller.media_info.media_type = MediaType.Video
1243+ return True
1244+ if suffix in player.audio_extensions_list:
1245+ if player.load(display):
1246+ self.curDisplayMediaPlayer[display] = player
1247+ controller.media_info.media_type = MediaType.Audio
1248+ return True
1249+ else:
1250+ for title in usedPlayers:
1251+ player = self.mediaPlayers[title]
1252+ if player.canFolder:
1253+ self.resize(controller, display, player)
1254+ if player.load(display):
1255+ self.curDisplayMediaPlayer[display] = player
1256+ controller.media_info.media_type = MediaType.Video
1257+ return True
1258+ # no valid player found
1259+ return False
1260+
1261+ def video_play(self, msg, status=True):
1262+ """
1263+ Responds to the request to play a loaded video
1264+
1265+ ``msg``
1266+ First element is the controller which should be used
1267+ """
1268+ log.debug(u'video_play')
1269+ controller = msg[0]
1270+ for display in self.curDisplayMediaPlayer.keys():
1271+ if display.controller == controller:
1272+ if not self.curDisplayMediaPlayer[display].play(display):
1273+ return False
1274+ if status:
1275+ display.frame.evaluateJavaScript(u'show_blank("desktop");')
1276+ self.curDisplayMediaPlayer[display].set_visible(display, True)
1277+ if controller.isLive:
1278+ if controller.hideMenu.defaultAction().isChecked():
1279+ controller.hideMenu.defaultAction().trigger()
1280+ # Start Timer for ui updates
1281+ if not self.timer.isActive():
1282+ self.timer.start()
1283+ return True
1284+
1285+ def video_pause(self, msg):
1286+ """
1287+ Responds to the request to pause a loaded video
1288+
1289+ ``msg``
1290+ First element is the controller which should be used
1291+ """
1292+ log.debug(u'video_pause')
1293+ controller = msg[0]
1294+ for display in self.curDisplayMediaPlayer.keys():
1295+ if display.controller == controller:
1296+ self.curDisplayMediaPlayer[display].pause(display)
1297+
1298+ def video_stop(self, msg):
1299+ """
1300+ Responds to the request to stop a loaded video
1301+
1302+ ``msg``
1303+ First element is the controller which should be used
1304+ """
1305+ log.debug(u'video_stop')
1306+ controller = msg[0]
1307+ for display in self.curDisplayMediaPlayer.keys():
1308+ if display.controller == controller:
1309+ display.frame.evaluateJavaScript(u'show_blank("black");')
1310+ self.curDisplayMediaPlayer[display].stop(display)
1311+ self.curDisplayMediaPlayer[display].set_visible(display, False)
1312+
1313+ def video_volume(self, msg):
1314+ """
1315+ Changes the volume of a running video
1316+
1317+ ``msg``
1318+ First element is the controller which should be used
1319+ """
1320+ controller = msg[0]
1321+ vol = msg[1][0]
1322+ log.debug(u'video_volume %d' % vol)
1323+ for display in self.curDisplayMediaPlayer.keys():
1324+ if display.controller == controller:
1325+ self.curDisplayMediaPlayer[display].volume(display, vol)
1326+
1327+ def video_seek(self, msg):
1328+ """
1329+ Responds to the request to change the seek Slider of a loaded video
1330+
1331+ ``msg``
1332+ First element is the controller which should be used
1333+ Second element is a list with the seek Value as first element
1334+ """
1335+ log.debug(u'video_seek')
1336+ controller = msg[0]
1337+ seekVal = msg[1][0]
1338+ for display in self.curDisplayMediaPlayer.keys():
1339+ if display.controller == controller:
1340+ self.curDisplayMediaPlayer[display].seek(display, seekVal)
1341+
1342+ def video_reset(self, controller):
1343+ """
1344+ Responds to the request to reset a loaded video
1345+ """
1346+ log.debug(u'video_reset')
1347+ for display in self.curDisplayMediaPlayer.keys():
1348+ if display.controller == controller:
1349+ display.override = {}
1350+ self.curDisplayMediaPlayer[display].reset(display)
1351+ self.curDisplayMediaPlayer[display].set_visible(display, False)
1352+ display.frame.evaluateJavaScript(u'show_video( \
1353+ "setBackBoard", null, null, null,"hidden");')
1354+ del self.curDisplayMediaPlayer[display]
1355+ self.set_controls_visible(controller, False)
1356+
1357+ def video_hide(self, msg):
1358+ """
1359+ Hide the related video Widget
1360+
1361+ ``msg``
1362+ First element is the boolean for Live indication
1363+ """
1364+ isLive = msg[1]
1365+ if isLive:
1366+ controller = self.parent.liveController
1367+ for display in self.curDisplayMediaPlayer.keys():
1368+ if display.controller == controller:
1369+ if self.curDisplayMediaPlayer[display] \
1370+ .state == MediaState.Playing:
1371+ self.curDisplayMediaPlayer[display].pause(display)
1372+ self.curDisplayMediaPlayer[display] \
1373+ .set_visible(display, False)
1374+
1375+ def video_blank(self, msg):
1376+ """
1377+ Blank the related video Widget
1378+
1379+ ``msg``
1380+ First element is the boolean for Live indication
1381+ Second element is the hide mode
1382+ """
1383+ isLive = msg[1]
1384+ hide_mode = msg[2]
1385+ if isLive:
1386+ Receiver.send_message(u'live_display_hide', hide_mode)
1387+ controller = self.parent.liveController
1388+ for display in self.curDisplayMediaPlayer.keys():
1389+ if display.controller == controller:
1390+ if self.curDisplayMediaPlayer[display] \
1391+ .state == MediaState.Playing:
1392+ self.curDisplayMediaPlayer[display].pause(display)
1393+ self.curDisplayMediaPlayer[display] \
1394+ .set_visible(display, False)
1395+
1396+ def video_unblank(self, msg):
1397+ """
1398+ Unblank the related video Widget
1399+
1400+ ``msg``
1401+ First element is not relevant in this context
1402+ Second element is the boolean for Live indication
1403+ """
1404+ Receiver.send_message(u'live_display_show')
1405+ isLive = msg[1]
1406+ if isLive:
1407+ controller = self.parent.liveController
1408+ for display in self.curDisplayMediaPlayer.keys():
1409+ if display.controller == controller:
1410+ if self.curDisplayMediaPlayer[display] \
1411+ .state == MediaState.Paused:
1412+ if self.curDisplayMediaPlayer[display].play(display):
1413+ self.curDisplayMediaPlayer[display] \
1414+ .set_visible(display, True)
1415+ # Start Timer for ui updates
1416+ if not self.timer.isActive():
1417+ self.timer.start()
1418+
1419+
1420+ def get_audio_extensions_list(self):
1421+ audio_list = []
1422+ for player in self.mediaPlayers.values():
1423+ if player.isActive:
1424+ for item in player.audio_extensions_list:
1425+ if not item in audio_list:
1426+ audio_list.append(item)
1427+ return audio_list
1428+
1429+ def get_video_extensions_list(self):
1430+ video_list = []
1431+ for player in self.mediaPlayers.values():
1432+ if player.isActive:
1433+ for item in player.video_extensions_list:
1434+ if not item in video_list:
1435+ video_list.append(item)
1436+ return video_list
1437+
1438+ def override_player(self, override_player):
1439+ playerSettings = str(QtCore.QSettings().value(u'media/players',
1440+ QtCore.QVariant(u'webkit')).toString())
1441+ usedPlayers = playerSettings.split(u',')
1442+ if override_player in usedPlayers:
1443+ self.overridenPlayer = override_player
1444+ else:
1445+ self.overridenPlayer = ''
1446+
1447+ def finalise(self):
1448+ self.timer.stop()
1449+ for controller in self.controller:
1450+ self.video_reset(controller)
1451
1452=== added file 'openlp/core/ui/media/phononplayer.py'
1453--- openlp/core/ui/media/phononplayer.py 1970-01-01 00:00:00 +0000
1454+++ openlp/core/ui/media/phononplayer.py 2011-12-01 18:30:31 +0000
1455@@ -0,0 +1,201 @@
1456+# -*- coding: utf-8 -*-
1457+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
1458+
1459+###############################################################################
1460+# OpenLP - Open Source Lyrics Projection #
1461+# --------------------------------------------------------------------------- #
1462+# Copyright (c) 2008-2011 Raoul Snyman #
1463+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
1464+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
1465+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
1466+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
1467+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
1468+# --------------------------------------------------------------------------- #
1469+# This program is free software; you can redistribute it and/or modify it #
1470+# under the terms of the GNU General Public License as published by the Free #
1471+# Software Foundation; version 2 of the License. #
1472+# #
1473+# This program is distributed in the hope that it will be useful, but WITHOUT #
1474+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1475+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1476+# more details. #
1477+# #
1478+# You should have received a copy of the GNU General Public License along #
1479+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1480+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1481+###############################################################################
1482+
1483+import logging
1484+import mimetypes
1485+from datetime import datetime
1486+
1487+from PyQt4 import QtCore, QtGui
1488+from PyQt4.phonon import Phonon
1489+
1490+from openlp.core.lib import Receiver
1491+from openlp.core.lib.mediaplayer import MediaPlayer
1492+from openlp.core.ui.media import MediaState
1493+
1494+log = logging.getLogger(__name__)
1495+
1496+ADDITIONAL_EXT = {
1497+ u'audio/ac3': [u'.ac3'],
1498+ u'audio/flac': [u'.flac'],
1499+ u'audio/x-m4a': [u'.m4a'],
1500+ u'audio/midi': [u'.mid', u'.midi'],
1501+ u'audio/x-mp3': [u'.mp3'],
1502+ u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
1503+ u'audio/qcelp': [u'.qcp'],
1504+ u'audio/x-wma': [u'.wma'],
1505+ u'audio/x-ms-wma': [u'.wma'],
1506+ u'video/x-flv': [u'.flv'],
1507+ u'video/x-matroska': [u'.mpv', u'.mkv'],
1508+ u'video/x-wmv': [u'.wmv'],
1509+ u'video/x-mpg': [u'.mpg'],
1510+ u'video/x-ms-wmv': [u'.wmv']}
1511+
1512+
1513+class PhononPlayer(MediaPlayer):
1514+ """
1515+ A specialised version of the MediaPlayer class, which provides a Phonon
1516+ display.
1517+ """
1518+
1519+ def __init__(self, parent):
1520+ MediaPlayer.__init__(self, parent, u'phonon')
1521+ self.parent = parent
1522+ self.additional_extensions = ADDITIONAL_EXT
1523+ mimetypes.init()
1524+ for mimetype in Phonon.BackendCapabilities.availableMimeTypes():
1525+ mimetype = unicode(mimetype)
1526+ if mimetype.startswith(u'audio/'):
1527+ self._addToList(self.audio_extensions_list, mimetype)
1528+ elif mimetype.startswith(u'video/'):
1529+ self._addToList(self.video_extensions_list, mimetype)
1530+
1531+ def _addToList(self, list, mimetype):
1532+ # Add all extensions which mimetypes provides us for supported types.
1533+ extensions = mimetypes.guess_all_extensions(unicode(mimetype))
1534+ for extension in extensions:
1535+ ext = u'*%s' % extension
1536+ if ext not in list:
1537+ list.append(ext)
1538+ log.info(u'MediaPlugin: %s extensions: %s' % (mimetype,
1539+ u' '.join(extensions)))
1540+ # Add extensions for this mimetype from self.additional_extensions.
1541+ # This hack clears mimetypes' and operating system's shortcomings
1542+ # by providing possibly missing extensions.
1543+ if mimetype in self.additional_extensions.keys():
1544+ for extension in self.additional_extensions[mimetype]:
1545+ ext = u'*%s' % extension
1546+ if ext not in list:
1547+ list.append(ext)
1548+ log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype,
1549+ u' '.join(self.additional_extensions[mimetype])))
1550+
1551+ def setup(self, display):
1552+ display.phononWidget = Phonon.VideoWidget(display)
1553+ display.phononWidget.resize(display.size())
1554+ display.mediaObject = Phonon.MediaObject(display)
1555+ Phonon.createPath(display.mediaObject, display.phononWidget)
1556+ if display.hasAudio:
1557+ display.audio = Phonon.AudioOutput( \
1558+ Phonon.VideoCategory, display.mediaObject)
1559+ Phonon.createPath(display.mediaObject, display.audio)
1560+ display.phononWidget.raise_()
1561+ display.phononWidget.hide()
1562+ self.hasOwnWidget = True
1563+
1564+ def check_available(self):
1565+ return True
1566+
1567+ def load(self, display):
1568+ log.debug(u'load vid in Phonon Controller')
1569+ controller = display.controller
1570+ volume = controller.media_info.volume
1571+ path = controller.media_info.file_info.absoluteFilePath()
1572+ display.mediaObject.setCurrentSource(Phonon.MediaSource(path))
1573+ if not self.media_state_wait(display, Phonon.StoppedState):
1574+ return False
1575+ self.volume(display, volume)
1576+ return True
1577+
1578+ def media_state_wait(self, display, mediaState):
1579+ """
1580+ Wait for the video to change its state
1581+ Wait no longer than 5 seconds.
1582+ """
1583+ start = datetime.now()
1584+ current_state = display.mediaObject.state()
1585+ while current_state != mediaState:
1586+ current_state = display.mediaObject.state()
1587+ if current_state == Phonon.ErrorState:
1588+ return False
1589+ Receiver.send_message(u'openlp_process_events')
1590+ if (datetime.now() - start).seconds > 5:
1591+ return False
1592+ return True
1593+
1594+ def resize(self, display):
1595+ display.phononWidget.resize(display.size())
1596+
1597+ def play(self, display):
1598+ controller = display.controller
1599+ start_time = 0
1600+ if display.mediaObject.state() != Phonon.PausedState and \
1601+ controller.media_info.start_time > 0:
1602+ start_time = controller.media_info.start_time
1603+ display.mediaObject.play()
1604+ if self.media_state_wait(display, Phonon.PlayingState):
1605+ if start_time > 0:
1606+ self.seek(display, controller.media_info.start_time*1000)
1607+ self.volume(display, controller.media_info.volume)
1608+ controller.media_info.length = \
1609+ int(display.mediaObject.totalTime()/1000)
1610+ controller.seekSlider.setMaximum(controller.media_info.length*1000)
1611+ self.state = MediaState.Playing
1612+ display.phononWidget.raise_()
1613+ return True
1614+ else:
1615+ return False
1616+
1617+ def pause(self, display):
1618+ display.mediaObject.pause()
1619+ if self.media_state_wait(display, Phonon.PausedState):
1620+ self.state = MediaState.Paused
1621+
1622+ def stop(self, display):
1623+ display.mediaObject.stop()
1624+ self.set_visible(display, False)
1625+ self.state = MediaState.Stopped
1626+
1627+ def volume(self, display, vol):
1628+ # 1.0 is the highest value
1629+ if display.hasAudio:
1630+ vol = float(vol) / float(100)
1631+ display.audio.setVolume(vol)
1632+
1633+ def seek(self, display, seekVal):
1634+ display.mediaObject.seek(seekVal)
1635+
1636+ def reset(self, display):
1637+ display.mediaObject.stop()
1638+ display.mediaObject.clearQueue()
1639+ self.set_visible(display, False)
1640+ display.phononWidget.setVisible(False)
1641+ self.state = MediaState.Off
1642+
1643+ def set_visible(self, display, status):
1644+ if self.hasOwnWidget:
1645+ display.phononWidget.setVisible(status)
1646+
1647+ def update_ui(self, display):
1648+ controller = display.controller
1649+ if controller.media_info.end_time > 0:
1650+ if display.mediaObject.currentTime() > \
1651+ controller.media_info.end_time*1000:
1652+ self.stop(display)
1653+ self.set_visible(display, False)
1654+ if not controller.seekSlider.isSliderDown():
1655+ controller.seekSlider.setSliderPosition( \
1656+ display.mediaObject.currentTime())
1657
1658=== added file 'openlp/core/ui/media/webkitplayer.py'
1659--- openlp/core/ui/media/webkitplayer.py 1970-01-01 00:00:00 +0000
1660+++ openlp/core/ui/media/webkitplayer.py 2011-12-01 18:30:31 +0000
1661@@ -0,0 +1,426 @@
1662+# -*- coding: utf-8 -*-
1663+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
1664+
1665+###############################################################################
1666+# OpenLP - Open Source Lyrics Projection #
1667+# --------------------------------------------------------------------------- #
1668+# Copyright (c) 2008-2011 Raoul Snyman #
1669+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
1670+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
1671+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
1672+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
1673+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
1674+# --------------------------------------------------------------------------- #
1675+# This program is free software; you can redistribute it and/or modify it #
1676+# under the terms of the GNU General Public License as published by the Free #
1677+# Software Foundation; version 2 of the License. #
1678+# #
1679+# This program is distributed in the hope that it will be useful, but WITHOUT #
1680+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1681+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1682+# more details. #
1683+# #
1684+# You should have received a copy of the GNU General Public License along #
1685+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1686+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1687+###############################################################################
1688+
1689+import logging
1690+
1691+from PyQt4 import QtCore, QtGui, QtWebKit
1692+
1693+from openlp.core.lib import OpenLPToolbar, translate
1694+from openlp.core.lib.mediaplayer import MediaPlayer
1695+from openlp.core.ui.media import MediaState
1696+
1697+log = logging.getLogger(__name__)
1698+
1699+VIDEO_CSS = u"""
1700+#videobackboard {
1701+ z-index:3;
1702+ background-color: black;
1703+}
1704+#video1 {
1705+ z-index:4;
1706+}
1707+#video2 {
1708+ z-index:4;
1709+}
1710+"""
1711+
1712+VIDEO_JS = u"""
1713+ var video_timer = null;
1714+ var current_video = '1';
1715+
1716+ function show_video(state, path, volume, loop, varVal){
1717+ // Note, the preferred method for looping would be to use the
1718+ // video tag loop attribute.
1719+ // But QtWebKit doesn't support this. Neither does it support the
1720+ // onended event, hence the setInterval()
1721+ // In addition, setting the currentTime attribute to zero to restart
1722+ // the video raises an INDEX_SIZE_ERROR: DOM Exception 1
1723+ // To complicate it further, sometimes vid.currentTime stops
1724+ // slightly short of vid.duration and vid.ended is intermittent!
1725+ //
1726+ // Note, currently the background may go black between loops. Not
1727+ // desirable. Need to investigate using two <video>'s, and hiding/
1728+ // preloading one, and toggle between the two when looping.
1729+
1730+ if(current_video=='1'){
1731+ var vid = document.getElementById('video1');
1732+ var vid2 = document.getElementById('video2');
1733+ } else {
1734+ var vid = document.getElementById('video2');
1735+ var vid2 = document.getElementById('video1');
1736+ }
1737+ if(volume != null){
1738+ vid.volume = volume;
1739+ vid2.volume = volume;
1740+ }
1741+ switch(state){
1742+ case 'init':
1743+ vid.src = 'file:///' + path;
1744+ vid2.src = 'file:///' + path;
1745+ if(loop == null) loop = false;
1746+ vid.looping = loop;
1747+ vid2.looping = loop;
1748+ vid.load();
1749+ break;
1750+ case 'load':
1751+ vid2.style.visibility = 'hidden';
1752+ vid2.load();
1753+ break;
1754+ case 'play':
1755+ vid.play();
1756+ if(vid.looping){
1757+ video_timer = setInterval(
1758+ function() {
1759+ show_video('poll');
1760+ }, 200);
1761+ }
1762+ break;
1763+ case 'pause':
1764+ if(video_timer!=null){
1765+ clearInterval(video_timer);
1766+ video_timer = null;
1767+ }
1768+ vid.pause();
1769+ break;
1770+ case 'stop':
1771+ show_video('pause');
1772+ vid.currentTime = 0;
1773+ break;
1774+ case 'poll':
1775+ if(vid.ended||vid.currentTime+0.2>vid.duration)
1776+ show_video('swap');
1777+ break;
1778+ case 'swap':
1779+ show_video('pause');
1780+ if(current_video=='1')
1781+ current_video = '2';
1782+ else
1783+ current_video = '1';
1784+ show_video('load');
1785+ show_video('play');
1786+ show_video('setVisible',null,null,null,'visible');
1787+ break;
1788+ case 'close':
1789+ show_video('stop');
1790+ vid.src = '';
1791+ vid2.src = '';
1792+ break;
1793+ case 'length':
1794+ return vid.duration;
1795+ case 'currentTime':
1796+ return vid.currentTime;
1797+ case 'seek':
1798+ // doesnt work currently
1799+ vid.currentTime = varVal;
1800+ break;
1801+ case 'setVisible':
1802+ vid.style.visibility = varVal;
1803+ break;
1804+ case 'setBackBoard':
1805+ var back = document.getElementById('videobackboard');
1806+ back.style.visibility = varVal;
1807+ break;
1808+ }
1809+ }
1810+"""
1811+
1812+VIDEO_HTML = u"""
1813+<div id="videobackboard" class="size" style="visibility:hidden"></div>
1814+<video id="video1" class="size" style="visibility:hidden" autobuffer preload>
1815+</video>
1816+<video id="video2" class="size" style="visibility:hidden" autobuffer preload>
1817+</video>
1818+"""
1819+
1820+FLASH_CSS = u"""
1821+#flash {
1822+ z-index:5;
1823+}
1824+"""
1825+
1826+FLASH_JS = u"""
1827+ function getFlashMovieObject(movieName)
1828+ {
1829+ if (window.document[movieName])
1830+ {
1831+ return window.document[movieName];
1832+ }
1833+ if (document.embeds && document.embeds[movieName])
1834+ return document.embeds[movieName];
1835+ }
1836+
1837+ function show_flash(state, path, volume, varVal){
1838+ var text = document.getElementById('flash');
1839+ var flashMovie = getFlashMovieObject("OpenLPFlashMovie");
1840+ var src = "src = 'file:///" + path + "'";
1841+ var view_parm = " wmode='opaque'" +
1842+ " width='100%%'" +
1843+ " height='100%%'";
1844+ var swf_parm = " name='OpenLPFlashMovie'" +
1845+ " autostart='true' loop='false' play='true'" +
1846+ " hidden='false' swliveconnect='true' allowscriptaccess='always'" +
1847+ " volume='" + volume + "'";
1848+
1849+ switch(state){
1850+ case 'load':
1851+ text.innerHTML = "<embed " + src + view_parm + swf_parm + "/>";
1852+ flashMovie = getFlashMovieObject("OpenLPFlashMovie");
1853+ flashMovie.Play();
1854+ break;
1855+ case 'play':
1856+ flashMovie.Play();
1857+ break;
1858+ case 'pause':
1859+ flashMovie.StopPlay();
1860+ break;
1861+ case 'stop':
1862+ flashMovie.StopPlay();
1863+ tempHtml = text.innerHTML;
1864+ text.innerHTML = '';
1865+ text.innerHTML = tempHtml;
1866+ break;
1867+ case 'close':
1868+ flashMovie.StopPlay();
1869+ text.innerHTML = '';
1870+ break;
1871+ case 'length':
1872+ return flashMovie.TotalFrames();
1873+ case 'currentTime':
1874+ return flashMovie.CurrentFrame();
1875+ case 'seek':
1876+// flashMovie.GotoFrame(varVal);
1877+ break;
1878+ case 'setVisible':
1879+ text.style.visibility = varVal;
1880+ break;
1881+ }
1882+ }
1883+"""
1884+
1885+FLASH_HTML = u"""
1886+<div id="flash" class="size" style="visibility:hidden"></div>
1887+"""
1888+
1889+VIDEO_EXT = [
1890+ u'*.3gp'
1891+ , u'*.3gpp'
1892+ , u'*.3g2'
1893+ , u'*.3gpp2'
1894+ , u'*.aac'
1895+ , u'*.flv'
1896+ , u'*.f4a'
1897+ , u'*.f4b'
1898+ , u'*.f4p'
1899+ , u'*.f4v'
1900+ , u'*.mov'
1901+ , u'*.m4a'
1902+ , u'*.m4b'
1903+ , u'*.m4p'
1904+ , u'*.m4v'
1905+ , u'*.mkv'
1906+ , u'*.mp4'
1907+ , u'*.ogv'
1908+ , u'*.webm'
1909+ , u'*.mpg', u'*.wmv', u'*.mpeg', u'*.avi'
1910+ , u'*.swf'
1911+ ]
1912+
1913+AUDIO_EXT = [
1914+ u'*.mp3'
1915+ , u'*.ogg'
1916+ ]
1917+
1918+
1919+class WebkitPlayer(MediaPlayer):
1920+ """
1921+ A specialised version of the MediaPlayer class, which provides a QtWebKit
1922+ display.
1923+ """
1924+
1925+ def __init__(self, parent):
1926+ MediaPlayer.__init__(self, parent, u'webkit')
1927+ self.parent = parent
1928+ self.canBackground = True
1929+ self.audio_extensions_list = AUDIO_EXT
1930+ self.video_extensions_list = VIDEO_EXT
1931+
1932+ def get_media_display_css(self):
1933+ """
1934+ Add css style sheets to htmlbuilder
1935+ """
1936+ return VIDEO_CSS + FLASH_CSS
1937+
1938+ def get_media_display_javascript(self):
1939+ """
1940+ Add javascript functions to htmlbuilder
1941+ """
1942+ return VIDEO_JS + FLASH_JS
1943+
1944+ def get_media_display_html(self):
1945+ """
1946+ Add html code to htmlbuilder
1947+ """
1948+ return VIDEO_HTML + FLASH_HTML
1949+
1950+ def setup(self, display):
1951+ display.webView.resize(display.size())
1952+ display.webView.raise_()
1953+ self.hasOwnWidget = False
1954+
1955+ def check_available(self):
1956+ return True
1957+
1958+ def load(self, display):
1959+ log.debug(u'load vid in Webkit Controller')
1960+ controller = display.controller
1961+ if display.hasAudio:
1962+ volume = controller.media_info.volume
1963+ vol = float(volume) / float(100)
1964+ else:
1965+ vol = 0
1966+ path = controller.media_info.file_info.absoluteFilePath()
1967+ if controller.media_info.is_background:
1968+ loop = u'true'
1969+ else:
1970+ loop = u'false'
1971+ display.webView.setVisible(True)
1972+ if controller.media_info.file_info.suffix() == u'swf':
1973+ controller.media_info.is_flash = True
1974+ js = u'show_flash("load","%s");' % \
1975+ (path.replace(u'\\', u'\\\\'))
1976+ else:
1977+ js = u'show_video("init", "%s", %s, %s);' % \
1978+ (path.replace(u'\\', u'\\\\'), str(vol), loop)
1979+ display.frame.evaluateJavaScript(js)
1980+ return True
1981+
1982+ def resize(self, display):
1983+ controller = display.controller
1984+ display.webView.resize(display.size())
1985+
1986+ def play(self, display):
1987+ controller = display.controller
1988+ display.webLoaded = True
1989+ length = 0
1990+ start_time = 0
1991+ if self.state != MediaState.Paused and \
1992+ controller.media_info.start_time > 0:
1993+ start_time = controller.media_info.start_time
1994+ self.set_visible(display, True)
1995+ if controller.media_info.is_flash:
1996+ display.frame.evaluateJavaScript(u'show_flash("play");')
1997+ else:
1998+ display.frame.evaluateJavaScript(u'show_video("play");')
1999+ if start_time > 0:
2000+ self.seek(display, controller.media_info.start_time*1000)
2001+ # TODO add playing check and get the correct media length
2002+ controller.media_info.length = length
2003+ self.state = MediaState.Playing
2004+ display.webView.raise_()
2005+ return True
2006+
2007+ def pause(self, display):
2008+ controller = display.controller
2009+ if controller.media_info.is_flash:
2010+ display.frame.evaluateJavaScript(u'show_flash("pause");')
2011+ else:
2012+ display.frame.evaluateJavaScript(u'show_video("pause");')
2013+ self.state = MediaState.Paused
2014+
2015+ def stop(self, display):
2016+ controller = display.controller
2017+ if controller.media_info.is_flash:
2018+ display.frame.evaluateJavaScript(u'show_flash("stop");')
2019+ else:
2020+ display.frame.evaluateJavaScript(u'show_video("stop");')
2021+ controller.seekSlider.setSliderPosition(0)
2022+ self.state = MediaState.Stopped
2023+
2024+ def volume(self, display, vol):
2025+ controller = display.controller
2026+ # 1.0 is the highest value
2027+ if display.hasAudio:
2028+ vol = float(vol) / float(100)
2029+ if not controller.media_info.is_flash:
2030+ display.frame.evaluateJavaScript(
2031+ u'show_video(null, null, %s);' % str(vol))
2032+
2033+ def seek(self, display, seekVal):
2034+ controller = display.controller
2035+ if controller.media_info.is_flash:
2036+ seek = seekVal
2037+ display.frame.evaluateJavaScript( \
2038+ u'show_flash("seek", null, null, "%s");' % (seek))
2039+ else:
2040+ seek = float(seekVal)/1000
2041+ display.frame.evaluateJavaScript( \
2042+ u'show_video("seek", null, null, null, "%f");' % (seek))
2043+
2044+ def reset(self, display):
2045+ controller = display.controller
2046+ if controller.media_info.is_flash:
2047+ display.frame.evaluateJavaScript(u'show_flash("close");')
2048+ else:
2049+ display.frame.evaluateJavaScript(u'show_video("close");')
2050+ self.state = MediaState.Off
2051+
2052+ def set_visible(self, display, status):
2053+ controller = display.controller
2054+ if status:
2055+ is_visible = "visible"
2056+ else:
2057+ is_visible = "hidden"
2058+ if controller.media_info.is_flash:
2059+ display.frame.evaluateJavaScript(u'show_flash( \
2060+ "setVisible", null, null, "%s");' % (is_visible))
2061+ else:
2062+ display.frame.evaluateJavaScript(u'show_video( \
2063+ "setVisible", null, null, null, "%s");' % (is_visible))
2064+
2065+ def update_ui(self, display):
2066+ controller = display.controller
2067+ if controller.media_info.is_flash:
2068+ currentTime = display.frame.evaluateJavaScript( \
2069+ u'show_flash("currentTime");').toInt()[0]
2070+ length = display.frame.evaluateJavaScript( \
2071+ u'show_flash("length");').toInt()[0]
2072+ else:
2073+ (currentTime, ok) = display.frame.evaluateJavaScript( \
2074+ u'show_video("currentTime");').toFloat()
2075+ # check if conversion was ok and value is not 'NaN'
2076+ if ok and currentTime != float('inf'):
2077+ currentTime = int(currentTime*1000)
2078+ (length, ok) = display.frame.evaluateJavaScript( \
2079+ u'show_video("length");').toFloat()
2080+ # check if conversion was ok and value is not 'NaN'
2081+ if ok and length != float('inf'):
2082+ length = int(length*1000)
2083+ if currentTime > 0:
2084+ controller.media_info.length = length
2085+ controller.seekSlider.setMaximum(length)
2086+ if not controller.seekSlider.isSliderDown():
2087+ controller.seekSlider.setSliderPosition(currentTime)
2088
2089=== modified file 'openlp/core/ui/slidecontroller.py'
2090--- openlp/core/ui/slidecontroller.py 2011-11-28 18:03:38 +0000
2091+++ openlp/core/ui/slidecontroller.py 2011-12-01 18:30:31 +0000
2092@@ -34,9 +34,9 @@
2093 from PyQt4.phonon import Phonon
2094
2095 from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \
2096- translate, build_icon
2097+ translate, build_icon, ServiceItem, build_html, PluginManager, ServiceItem
2098 from openlp.core.lib.ui import UiStrings, shortcut_action
2099-from openlp.core.ui import HideMode, MainDisplay, ScreenList
2100+from openlp.core.ui import HideMode, MainDisplay, Display, ScreenList
2101 from openlp.core.utils.actions import ActionList, CategoryOrder
2102
2103 log = logging.getLogger(__name__)
2104@@ -49,8 +49,29 @@
2105 def __init__(self, parent=None, name=None):
2106 QtGui.QTableWidget.__init__(self, parent.controller)
2107
2108-
2109-class SlideController(QtGui.QWidget):
2110+class Controller(QtGui.QWidget):
2111+ """
2112+ Controller is a general controller widget.
2113+ """
2114+ def __init__(self, parent, isLive=False):
2115+ """
2116+ Set up the general Controller.
2117+ """
2118+ QtGui.QWidget.__init__(self, parent)
2119+ self.isLive = isLive
2120+ self.display = None
2121+
2122+ def sendToPlugins(self, *args):
2123+ """
2124+ This is the generic function to send signal for control widgets,
2125+ created from within other plugins
2126+ This function is needed to catch the current controller
2127+ """
2128+ sender = self.sender().objectName() if self.sender().objectName() else self.sender().text()
2129+ controller = self
2130+ Receiver.send_message('%s' % sender, [controller, args])
2131+
2132+class SlideController(Controller):
2133 """
2134 SlideController is the slide controller widget. This widget is what the
2135 user uses to control the displaying of verses/slides/etc on the screen.
2136@@ -59,13 +80,12 @@
2137 """
2138 Set up the Slide Controller.
2139 """
2140- QtGui.QWidget.__init__(self, parent)
2141- self.isLive = isLive
2142- self.display = None
2143+ Controller.__init__(self, parent, isLive)
2144 self.screens = ScreenList.get_instance()
2145 self.ratio = float(self.screens.current[u'size'].width()) / \
2146 float(self.screens.current[u'size'].height())
2147 self.imageManager = self.parent().imageManager
2148+ self.mediaController = self.parent().mediaController
2149 self.loopList = [
2150 u'Play Slides Menu',
2151 u'Loop Separator',
2152@@ -74,7 +94,10 @@
2153 self.songEditList = [
2154 u'Edit Song',
2155 ]
2156- self.volume = 10
2157+ self.nextPreviousList = [
2158+ u'Previous Slide',
2159+ u'Next Slide'
2160+ ]
2161 self.timer_id = 0
2162 self.songEdit = False
2163 self.selectedRow = 0
2164@@ -140,14 +163,14 @@
2165 self.toolbar.sizePolicy().hasHeightForWidth())
2166 self.toolbar.setSizePolicy(sizeToolbarPolicy)
2167 self.previousItem = self.toolbar.addToolbarButton(
2168- translate('OpenLP.SlideController', 'Previous Slide'),
2169+ u'Previous Slide',
2170 u':/slides/slide_previous.png',
2171 translate('OpenLP.SlideController', 'Move to previous.'),
2172 self.onSlideSelectedPrevious,
2173 shortcuts=[QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp],
2174 context=QtCore.Qt.WidgetWithChildrenShortcut)
2175 self.nextItem = self.toolbar.addToolbarButton(
2176- translate('OpenLP.SlideController', 'Next Slide'),
2177+ u'Next Slide',
2178 u':/slides/slide_next.png',
2179 translate('OpenLP.SlideController', 'Move to next.'),
2180 self.onSlideSelectedNext,
2181@@ -234,20 +257,8 @@
2182 'Edit and reload song preview.'),
2183 self.onEditSong)
2184 self.controllerLayout.addWidget(self.toolbar)
2185- # Build a Media ToolBar
2186- self.mediabar = OpenLPToolbar(self)
2187- self.mediabar.addToolbarButton(
2188- u'Media Start', u':/slides/media_playback_start.png',
2189- translate('OpenLP.SlideController', 'Start playing media.'),
2190- self.onMediaPlay)
2191- self.mediabar.addToolbarButton(
2192- u'Media Pause', u':/slides/media_playback_pause.png',
2193- translate('OpenLP.SlideController', 'Start playing media.'),
2194- self.onMediaPause)
2195- self.mediabar.addToolbarButton(
2196- u'Media Stop', u':/slides/media_playback_stop.png',
2197- translate('OpenLP.SlideController', 'Start playing media.'),
2198- self.onMediaStop)
2199+ # Build the Media Toolbar
2200+ self.mediaController.add_controller_items(self, self.controllerLayout)
2201 if self.isLive:
2202 # Build the Song Toolbar
2203 self.songMenu = QtGui.QToolButton(self.toolbar)
2204@@ -263,23 +274,6 @@
2205 translate('OpenLP.SlideController', 'Pause audio.'),
2206 self.onAudioPauseClicked, True)
2207 self.audioPauseItem.setVisible(False)
2208- # Build the volumeSlider.
2209- self.volumeSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
2210- self.volumeSlider.setTickInterval(1)
2211- self.volumeSlider.setTickPosition(QtGui.QSlider.TicksAbove)
2212- self.volumeSlider.setMinimum(0)
2213- self.volumeSlider.setMaximum(10)
2214- else:
2215- # Build the seekSlider.
2216- self.seekSlider = Phonon.SeekSlider()
2217- self.seekSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
2218- self.seekSlider.setObjectName(u'seekSlider')
2219- self.mediabar.addToolbarWidget(u'Seek Slider', self.seekSlider)
2220- self.volumeSlider = Phonon.VolumeSlider()
2221- self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
2222- self.volumeSlider.setObjectName(u'volumeSlider')
2223- self.mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
2224- self.controllerLayout.addWidget(self.mediabar)
2225 # Screen preview area
2226 self.previewFrame = QtGui.QFrame(self.splitter)
2227 self.previewFrame.setGeometry(QtCore.QRect(0, 0, 300, 300 * self.ratio))
2228@@ -296,17 +290,13 @@
2229 self.slideLayout = QtGui.QVBoxLayout()
2230 self.slideLayout.setSpacing(0)
2231 self.slideLayout.setMargin(0)
2232- self.slideLayout.setObjectName(u'slideLayout')
2233- if not self.isLive:
2234- self.mediaObject = Phonon.MediaObject(self)
2235- self.video = Phonon.VideoWidget()
2236- self.video.setVisible(False)
2237- self.audio = Phonon.AudioOutput(Phonon.VideoCategory,
2238- self.mediaObject)
2239- Phonon.createPath(self.mediaObject, self.video)
2240- Phonon.createPath(self.mediaObject, self.audio)
2241- self.video.setGeometry(QtCore.QRect(0, 0, 300, 225))
2242- self.slideLayout.insertWidget(0, self.video)
2243+ self.slideLayout.setObjectName(u'SlideLayout')
2244+ self.previewDisplay = Display(self, self.isLive, self)
2245+ self.previewDisplay.setGeometry(QtCore.QRect(0, 0, 300, 300))
2246+ self.previewDisplay.screen = {u'size':self.previewDisplay.geometry()}
2247+ self.previewDisplay.setup()
2248+ self.slideLayout.insertWidget(0, self.previewDisplay)
2249+ self.previewDisplay.hide()
2250 # Actual preview screen
2251 self.slidePreview = QtGui.QLabel(self)
2252 sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed,
2253@@ -415,8 +405,6 @@
2254 QtCore.QObject.connect(self.previewListWidget,
2255 QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected)
2256 if self.isLive:
2257- QtCore.QObject.connect(self.volumeSlider,
2258- QtCore.SIGNAL(u'sliderReleased()'), self.mediaVolume)
2259 QtCore.QObject.connect(Receiver.get_receiver(),
2260 QtCore.SIGNAL(u'slidecontroller_live_spin_delay'),
2261 self.receiveSpinDelay)
2262@@ -426,7 +414,6 @@
2263 QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
2264 self.onGoLiveClick)
2265 self.toolbar.makeWidgetsInvisible(self.songEditList)
2266- self.mediabar.setVisible(False)
2267 if self.isLive:
2268 self.setLiveHotkeys(self)
2269 self.__addActionsToWidget(self.previewListWidget)
2270@@ -577,7 +564,7 @@
2271
2272 def liveEscape(self):
2273 self.display.setVisible(False)
2274- self.display.videoStop()
2275+ self.mediaController.video_stop([self])
2276
2277 def servicePrevious(self):
2278 """
2279@@ -617,14 +604,22 @@
2280 # rebuild display as screen size changed
2281 if self.display:
2282 self.display.close()
2283- self.display = MainDisplay(self, self.imageManager, self.isLive)
2284+ self.display = MainDisplay(self, self.imageManager, self.isLive,
2285+ self)
2286 self.display.setup()
2287 if self.isLive:
2288 self.__addActionsToWidget(self.display)
2289 # The SlidePreview's ratio.
2290 self.ratio = float(self.screens.current[u'size'].width()) / \
2291 float(self.screens.current[u'size'].height())
2292+ self.mediaController.setup_display(self.display)
2293 self.previewSizeChanged()
2294+ self.previewDisplay.setup()
2295+ serviceItem = ServiceItem()
2296+ self.previewDisplay.webView.setHtml(build_html(serviceItem,
2297+ self.previewDisplay.screen, None, self.isLive, None,
2298+ plugins=PluginManager.get_instance().plugins))
2299+ self.mediaController.setup_display(self.previewDisplay)
2300 if self.serviceItem:
2301 self.refreshServiceItem()
2302
2303@@ -646,11 +641,17 @@
2304 max_height = self.previewFrame.height() - self.grid.margin() * 2
2305 self.slidePreview.setFixedSize(QtCore.QSize(max_height * self.ratio,
2306 max_height))
2307+ self.previewDisplay.setFixedSize(QtCore.QSize(max_height * self.ratio,
2308+ max_height))
2309+ self.previewDisplay.screen = {u'size':self.previewDisplay.geometry()}
2310 else:
2311 # We have to take the width as limit.
2312 max_width = self.previewFrame.width() - self.grid.margin() * 2
2313 self.slidePreview.setFixedSize(QtCore.QSize(max_width,
2314 max_width / self.ratio))
2315+ self.previewDisplay.setFixedSize(QtCore.QSize(max_width,
2316+ max_width / self.ratio))
2317+ self.previewDisplay.screen = {u'size':self.previewDisplay.geometry()}
2318 # Make sure that the frames have the correct size.
2319 self.previewListWidget.setColumnWidth(0,
2320 self.previewListWidget.viewport().size().width())
2321@@ -711,12 +712,13 @@
2322 len(item.get_frames()) > 1:
2323 self.toolbar.makeWidgetsVisible(self.loopList)
2324 if item.is_media():
2325- self.toolbar.setVisible(False)
2326 self.mediabar.setVisible(True)
2327+ self.toolbar.makeWidgetsInvisible(self.nextPreviousList)
2328 else:
2329 # Work-around for OS X, hide and then show the toolbar
2330 # See bug #791050
2331- self.toolbar.show()
2332+ self.toolbar.makeWidgetsVisible(self.nextPreviousList)
2333+ self.toolbar.show()
2334
2335 def enablePreviewToolBar(self, item):
2336 """
2337@@ -730,13 +732,13 @@
2338 if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin:
2339 self.toolbar.makeWidgetsVisible(self.songEditList)
2340 elif item.is_media():
2341- self.toolbar.setVisible(False)
2342 self.mediabar.setVisible(True)
2343- self.volumeSlider.setAudioOutput(self.audio)
2344+ self.toolbar.makeWidgetsInvisible(self.nextPreviousList)
2345 if not item.is_media():
2346 # Work-around for OS X, hide and then show the toolbar
2347 # See bug #791050
2348- self.toolbar.show()
2349+ self.toolbar.makeWidgetsVisible(self.nextPreviousList)
2350+ self.toolbar.show()
2351
2352 def refreshServiceItem(self):
2353 """
2354@@ -1325,72 +1327,18 @@
2355 """
2356 log.debug(u'SlideController onMediaStart')
2357 file = os.path.join(item.get_frame_path(), item.get_frame_title())
2358- if self.isLive:
2359- self.display.video(file, self.volume)
2360- self.volumeSlider.setValue(self.volume)
2361- else:
2362- self.mediaObject.stop()
2363- self.mediaObject.clearQueue()
2364- self.mediaObject.setCurrentSource(Phonon.MediaSource(file))
2365- self.seekSlider.setMediaObject(self.mediaObject)
2366- self.seekSlider.show()
2367- self.onMediaPlay()
2368-
2369- def mediaVolume(self):
2370- """
2371- Respond to the release of Volume Slider
2372- """
2373- log.debug(u'SlideController mediaVolume')
2374- self.volume = self.volumeSlider.value()
2375- self.display.videoVolume(self.volume)
2376-
2377- def onMediaPause(self):
2378- """
2379- Respond to the Pause from the media Toolbar
2380- """
2381- log.debug(u'SlideController onMediaPause')
2382- if self.isLive:
2383- self.display.videoPause()
2384- else:
2385- self.mediaObject.pause()
2386-
2387- def onMediaPlay(self):
2388- """
2389- Respond to the Play from the media Toolbar
2390- """
2391- log.debug(u'SlideController onMediaPlay')
2392- if self.isLive:
2393- self.display.videoPlay()
2394- else:
2395+ self.mediaController.video(self, file, False, False)
2396+ if not self.isLive or self.mediaController.withLivePreview:
2397+ self.previewDisplay.show()
2398 self.slidePreview.hide()
2399- self.video.show()
2400- self.mediaObject.play()
2401-
2402- def onMediaStop(self):
2403- """
2404- Respond to the Stop from the media Toolbar
2405- """
2406- log.debug(u'SlideController onMediaStop')
2407- if self.isLive:
2408- self.display.videoStop()
2409- else:
2410- self.mediaObject.stop()
2411- self.video.hide()
2412- self.slidePreview.clear()
2413- self.slidePreview.show()
2414
2415 def onMediaClose(self):
2416 """
2417 Respond to a request to close the Video
2418 """
2419- log.debug(u'SlideController onMediaStop')
2420- if self.isLive:
2421- self.display.resetVideo()
2422- else:
2423- self.mediaObject.stop()
2424- self.mediaObject.clearQueue()
2425- self.video.hide()
2426- self.slidePreview.clear()
2427+ log.debug(u'SlideController onMediaClose')
2428+ self.mediaController.video_reset(self)
2429+ self.previewDisplay.hide()
2430 self.slidePreview.show()
2431
2432 def _resetBlank(self):
2433
2434=== modified file 'openlp/plugins/media/lib/mediaitem.py'
2435--- openlp/plugins/media/lib/mediaitem.py 2011-10-15 11:30:39 +0000
2436+++ openlp/plugins/media/lib/mediaitem.py 2011-12-01 18:30:31 +0000
2437@@ -25,7 +25,6 @@
2438 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
2439 ###############################################################################
2440
2441-from datetime import datetime
2442 import logging
2443 import os
2444 import locale
2445@@ -34,12 +33,17 @@
2446 from PyQt4.phonon import Phonon
2447
2448 from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
2449- SettingsManager, translate, check_item_selected, Receiver, MediaType
2450-from openlp.core.lib.ui import UiStrings, critical_error_message_box
2451+ SettingsManager, translate, check_item_selected, Receiver, MediaType, \
2452+ ServiceItem, build_html
2453+from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
2454+ media_item_combo_box
2455+from openlp.core.ui import Controller, Display
2456
2457 log = logging.getLogger(__name__)
2458
2459 CLAPPERBOARD = QtGui.QImage(u':/media/media_video.png')
2460+#TODO: Add an appropriate Icon for DVDs, CDs, ...
2461+DVD_ICON = QtGui.QImage(u':/media/media_video.png')
2462
2463 class MediaMediaItem(MediaManagerItem):
2464 """
2465@@ -51,16 +55,40 @@
2466 self.iconPath = u'images/image'
2467 self.background = False
2468 self.previewFunction = CLAPPERBOARD
2469+ self.Automatic = u''
2470 MediaManagerItem.__init__(self, parent, plugin, icon)
2471 self.singleServiceItem = False
2472 self.hasSearch = True
2473 self.mediaObject = None
2474+ self.mediaController = Controller(parent)
2475+ self.mediaController.controllerLayout = QtGui.QVBoxLayout()
2476+ self.plugin.mediaController.add_controller_items(self.mediaController, \
2477+ self.mediaController.controllerLayout)
2478+ self.plugin.mediaController.set_controls_visible(self.mediaController, \
2479+ False)
2480+ self.mediaController.previewDisplay = Display(self.mediaController, \
2481+ False, self.mediaController)
2482+ self.mediaController.previewDisplay.setGeometry(
2483+ QtCore.QRect(0, 0, 300, 300))
2484+ self.mediaController.previewDisplay.screen = \
2485+ {u'size':self.mediaController.previewDisplay.geometry()}
2486+ self.mediaController.previewDisplay.setup()
2487+ serviceItem = ServiceItem()
2488+ self.mediaController.previewDisplay.webView.setHtml(build_html( \
2489+ serviceItem, self.mediaController.previewDisplay.screen, None, \
2490+ False, None))
2491+ self.mediaController.previewDisplay.setup()
2492+ self.plugin.mediaController.setup_display( \
2493+ self.mediaController.previewDisplay)
2494+ self.mediaController.previewDisplay.hide()
2495+
2496 QtCore.QObject.connect(Receiver.get_receiver(),
2497 QtCore.SIGNAL(u'video_background_replaced'),
2498 self.videobackgroundReplaced)
2499 QtCore.QObject.connect(Receiver.get_receiver(),
2500- QtCore.SIGNAL(u'openlp_phonon_creation'),
2501- self.createPhonon)
2502+ QtCore.SIGNAL(u'mediaitem_media_rebuild'), self.rebuild)
2503+ QtCore.QObject.connect(Receiver.get_receiver(),
2504+ QtCore.SIGNAL(u'config_screen_changed'), self.displaySetup)
2505 # Allow DnD from the desktop
2506 self.listView.activateDnD()
2507
2508@@ -74,6 +102,10 @@
2509 self.replaceAction.setToolTip(UiStrings().ReplaceLiveBG)
2510 self.resetAction.setText(UiStrings().ResetBG)
2511 self.resetAction.setToolTip(UiStrings().ResetLiveBG)
2512+ self.Automatic = translate('MediaPlugin.MediaItem',
2513+ 'Automatic')
2514+ self.displayTypeLabel.setText(
2515+ translate('MediaPlugin.MediaItem', 'Use Player:'))
2516
2517 def requiredIcons(self):
2518 MediaManagerItem.requiredIcons(self)
2519@@ -92,13 +124,34 @@
2520 self.resetAction = self.addToolbarButton(u'', u'',
2521 u':/system/system_close.png', self.onResetClick, False)
2522 self.resetAction.setVisible(False)
2523+ self.mediaWidget = QtGui.QWidget(self)
2524+ self.mediaWidget.setObjectName(u'mediaWidget')
2525+ self.displayLayout = QtGui.QFormLayout(self.mediaWidget)
2526+ self.displayLayout.setMargin(self.displayLayout.spacing())
2527+ self.displayLayout.setObjectName(u'displayLayout')
2528+ self.displayTypeLabel = QtGui.QLabel(self.mediaWidget)
2529+ self.displayTypeLabel.setObjectName(u'displayTypeLabel')
2530+ self.displayTypeComboBox = media_item_combo_box(
2531+ self.mediaWidget, u'displayTypeComboBox')
2532+ self.displayTypeLabel.setBuddy(self.displayTypeComboBox)
2533+ self.displayLayout.addRow(self.displayTypeLabel,
2534+ self.displayTypeComboBox)
2535+ # Add the Media widget to the page layout
2536+ self.pageLayout.addWidget(self.mediaWidget)
2537+ QtCore.QObject.connect(self.displayTypeComboBox,
2538+ QtCore.SIGNAL(u'currentIndexChanged (int)'), self.overridePlayerChanged)
2539+
2540+ def overridePlayerChanged(self, index):
2541+ Receiver.send_message(u'media_override_player', \
2542+ u'%s' % self.displayTypeComboBox.currentText())
2543
2544 def onResetClick(self):
2545 """
2546- Called to reset the Live backgound with the media selected.
2547+ Called to reset the Live background with the media selected,
2548 """
2549+ self.plugin.liveController.mediaController.video_reset( \
2550+ self.plugin.liveController)
2551 self.resetAction.setVisible(False)
2552- self.plugin.liveController.display.resetVideo()
2553
2554 def videobackgroundReplaced(self):
2555 """
2556@@ -108,7 +161,7 @@
2557
2558 def onReplaceClick(self):
2559 """
2560- Called to replace Live backgound with the media selected.
2561+ Called to replace Live background with the media selected.
2562 """
2563 if check_item_selected(self.listView,
2564 translate('MediaPlugin.MediaItem',
2565@@ -116,8 +169,8 @@
2566 item = self.listView.currentItem()
2567 filename = unicode(item.data(QtCore.Qt.UserRole).toString())
2568 if os.path.exists(filename):
2569- (path, name) = os.path.split(filename)
2570- if self.plugin.liveController.display.video(filename, 0, True):
2571+ if self.plugin.liveController.mediaController.video( \
2572+ self.plugin.liveController, filename, True, True):
2573 self.resetAction.setVisible(True)
2574 else:
2575 critical_error_message_box(UiStrings().LiveBGError,
2576@@ -144,30 +197,18 @@
2577 unicode(translate('MediaPlugin.MediaItem',
2578 'The file %s no longer exists.')) % filename)
2579 return False
2580- self.mediaObject.stop()
2581- self.mediaObject.clearQueue()
2582- self.mediaObject.setCurrentSource(Phonon.MediaSource(filename))
2583- if not self.mediaStateWait(Phonon.StoppedState):
2584- critical_error_message_box(UiStrings().UnsupportedFile,
2585- UiStrings().UnsupportedFile)
2586+ self.mediaLength = 0
2587+ if self.plugin.mediaController.video( \
2588+ self.mediaController, filename, False, False):
2589+ self.mediaLength = self.mediaController.media_info.length
2590+ service_item.media_length = self.mediaLength
2591+ self.plugin.mediaController.video_reset(self.mediaController)
2592+ if self.mediaLength > 0:
2593+ service_item.add_capability(
2594+ ItemCapabilities.HasVariableStartTime)
2595+ else:
2596 return False
2597- # File too big for processing
2598- if os.path.getsize(filename) <= 52428800: # 50MiB
2599- self.mediaObject.play()
2600- if not self.mediaStateWait(Phonon.PlayingState) \
2601- or self.mediaObject.currentSource().type() \
2602- == Phonon.MediaSource.Invalid:
2603- self.mediaObject.stop()
2604- critical_error_message_box(
2605- translate('MediaPlugin.MediaItem', 'File Too Big'),
2606- translate('MediaPlugin.MediaItem', 'The file you are '
2607- 'trying to load is too big. Please reduce it to less '
2608- 'than 50MiB.'))
2609- return False
2610- self.mediaObject.stop()
2611- service_item.media_length = self.mediaObject.totalTime() / 1000
2612- service_item.add_capability(
2613- ItemCapabilities.HasVariableStartTime)
2614+ service_item.media_length = self.mediaLength
2615 service_item.title = unicode(self.plugin.nameStrings[u'singular'])
2616 service_item.add_capability(ItemCapabilities.RequiresMedia)
2617 # force a non-existent theme
2618@@ -177,23 +218,49 @@
2619 service_item.add_from_command(path, name, frame)
2620 return True
2621
2622- def mediaStateWait(self, mediaState):
2623- """
2624- Wait for the video to change its state. Wait no longer than 5 seconds.
2625- """
2626- start = datetime.now()
2627- while self.mediaObject.state() != mediaState:
2628- if self.mediaObject.state() == Phonon.ErrorState:
2629- return False
2630- Receiver.send_message(u'openlp_process_events')
2631- if (datetime.now() - start).seconds > 5:
2632- return False
2633- return True
2634-
2635 def initialise(self):
2636 self.listView.clear()
2637 self.listView.setIconSize(QtCore.QSize(88, 50))
2638 self.loadList(SettingsManager.load_list(self.settingsSection, u'media'))
2639+ self.populateDisplayTypes()
2640+
2641+ def rebuild(self):
2642+ """
2643+ Rebuild the tab in the media manager when changes are made in
2644+ the settings
2645+ """
2646+ self.populateDisplayTypes()
2647+ self.onNewFileMasks = unicode(translate('MediaPlugin.MediaItem',
2648+ 'Videos (%s);;Audio (%s);;%s (*)')) % (
2649+ u' '.join(self.plugin.video_extensions_list),
2650+ u' '.join(self.plugin.audio_extensions_list), UiStrings().AllFiles)
2651+
2652+ def displaySetup(self):
2653+ self.plugin.mediaController.setup_display( \
2654+ self.mediaController.previewDisplay)
2655+
2656+
2657+ def populateDisplayTypes(self):
2658+ """
2659+ Load the combobox with the enabled media players,
2660+ allowing user to select a specific player if settings allow
2661+ """
2662+ self.displayTypeComboBox.clear()
2663+ playerSettings = str(QtCore.QSettings().value(u'media/players',
2664+ QtCore.QVariant(u'webkit')).toString())
2665+ usedPlayers = playerSettings.split(u',')
2666+ for title in usedPlayers:
2667+ # load the drop down selection
2668+ self.displayTypeComboBox.addItem(title)
2669+ if self.displayTypeComboBox.count() > 1:
2670+ self.displayTypeComboBox.insertItem(0, self.Automatic)
2671+ self.displayTypeComboBox.setCurrentIndex(0)
2672+ if QtCore.QSettings().value(self.settingsSection + u'/override player',
2673+ QtCore.QVariant(QtCore.Qt.Unchecked)) == QtCore.Qt.Checked:
2674+ self.mediaWidget.show()
2675+ else:
2676+ self.mediaWidget.hide()
2677+
2678
2679 def onDeleteClick(self):
2680 """
2681@@ -214,10 +281,18 @@
2682 media.sort(cmp=locale.strcoll,
2683 key=lambda filename: os.path.split(unicode(filename))[1].lower())
2684 for track in media:
2685- filename = os.path.split(unicode(track))[1]
2686- item_name = QtGui.QListWidgetItem(filename)
2687- item_name.setIcon(build_icon(CLAPPERBOARD))
2688- item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track))
2689+ track_info = QtCore.QFileInfo(track)
2690+ if not track_info.isFile():
2691+ filename = os.path.split(unicode(track))[1]
2692+ item_name = QtGui.QListWidgetItem(filename)
2693+ item_name.setIcon(build_icon(CLAPPERBOARD))
2694+ item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track))
2695+ else:
2696+ filename = os.path.split(unicode(track))[1]
2697+ item_name = QtGui.QListWidgetItem(filename)
2698+ #TODO: add the appropriate Icon
2699+ #item_name.setIcon(build_icon(DVD_ICON))
2700+ item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track))
2701 item_name.setToolTip(track)
2702 self.listView.addItem(item_name)
2703
2704@@ -234,11 +309,6 @@
2705 media = filter(lambda x: os.path.splitext(x)[1] in ext, media)
2706 return media
2707
2708- def createPhonon(self):
2709- log.debug(u'CreatePhonon')
2710- if not self.mediaObject:
2711- self.mediaObject = Phonon.MediaObject(self)
2712-
2713 def search(self, string):
2714 files = SettingsManager.load_list(self.settingsSection, u'media')
2715 results = []
2716
2717=== modified file 'openlp/plugins/media/lib/mediatab.py'
2718--- openlp/plugins/media/lib/mediatab.py 2011-06-12 16:02:52 +0000
2719+++ openlp/plugins/media/lib/mediatab.py 2011-12-01 18:30:31 +0000
2720@@ -28,51 +28,181 @@
2721 from PyQt4 import QtCore, QtGui
2722
2723 from openlp.core.lib import SettingsTab, translate, Receiver
2724+from openlp.core.lib.ui import UiStrings, critical_error_message_box
2725
2726 class MediaTab(SettingsTab):
2727 """
2728 MediaTab is the Media settings tab in the settings dialog.
2729 """
2730- def __init__(self, parent, title, visible_title, icon_path):
2731+ def __init__(self, parent, title, visible_title, media_players, icon_path):
2732+ self.media_players = media_players
2733 SettingsTab.__init__(self, parent, title, visible_title, icon_path)
2734
2735 def setupUi(self):
2736 self.setObjectName(u'MediaTab')
2737 SettingsTab.setupUi(self)
2738- self.mediaModeGroupBox = QtGui.QGroupBox(self.leftColumn)
2739- self.mediaModeGroupBox.setObjectName(u'mediaModeGroupBox')
2740- self.mediaModeLayout = QtGui.QFormLayout(self.mediaModeGroupBox)
2741- self.mediaModeLayout.setObjectName(u'mediaModeLayout')
2742- self.usePhononCheckBox = QtGui.QCheckBox(self.mediaModeGroupBox)
2743- self.usePhononCheckBox.setObjectName(u'usePhononCheckBox')
2744- self.mediaModeLayout.addRow(self.usePhononCheckBox)
2745- self.leftLayout.addWidget(self.mediaModeGroupBox)
2746+ self.mediaPlayerGroupBox = QtGui.QGroupBox(self.leftColumn)
2747+ self.mediaPlayerGroupBox.setObjectName(u'mediaPlayerGroupBox')
2748+ self.mediaPlayerLayout = QtGui.QVBoxLayout(self.mediaPlayerGroupBox)
2749+ self.mediaPlayerLayout.setObjectName(u'mediaPlayerLayout')
2750+ self.PlayerCheckBoxes = {}
2751+ for key in self.media_players:
2752+ player = self.media_players[key]
2753+ checkbox = QtGui.QCheckBox(self.mediaPlayerGroupBox)
2754+ checkbox.setEnabled(player.available)
2755+ checkbox.setObjectName(player.name + u'CheckBox')
2756+ self.PlayerCheckBoxes[player.name] = checkbox
2757+ self.mediaPlayerLayout.addWidget(checkbox)
2758+ self.leftLayout.addWidget(self.mediaPlayerGroupBox)
2759+ self.playerOrderGroupBox = QtGui.QGroupBox(self.leftColumn)
2760+ self.playerOrderGroupBox.setObjectName(u'playerOrderGroupBox')
2761+ self.playerOrderLayout = QtGui.QVBoxLayout(self.playerOrderGroupBox)
2762+ self.playerOrderLayout.setObjectName(u'playerOrderLayout')
2763+ self.playerOrderlistWidget = QtGui.QListWidget( \
2764+ self.playerOrderGroupBox)
2765+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
2766+ QtGui.QSizePolicy.Expanding)
2767+ sizePolicy.setHorizontalStretch(0)
2768+ sizePolicy.setVerticalStretch(0)
2769+ sizePolicy.setHeightForWidth(self.playerOrderlistWidget. \
2770+ sizePolicy().hasHeightForWidth())
2771+ self.playerOrderlistWidget.setSizePolicy(sizePolicy)
2772+ self.playerOrderlistWidget.setVerticalScrollBarPolicy( \
2773+ QtCore.Qt.ScrollBarAsNeeded)
2774+ self.playerOrderlistWidget.setHorizontalScrollBarPolicy( \
2775+ QtCore.Qt.ScrollBarAlwaysOff)
2776+ self.playerOrderlistWidget.setEditTriggers( \
2777+ QtGui.QAbstractItemView.NoEditTriggers)
2778+ self.playerOrderlistWidget.setObjectName(u'playerOrderlistWidget')
2779+ self.playerOrderLayout.addWidget(self.playerOrderlistWidget)
2780+ self.orderingButtonsWidget = QtGui.QWidget(self.playerOrderGroupBox)
2781+ self.orderingButtonsWidget.setObjectName(u'orderingButtonsWidget')
2782+ self.orderingButtonLayout = QtGui.QHBoxLayout( \
2783+ self.orderingButtonsWidget)
2784+ self.orderingButtonLayout.setObjectName(u'orderingButtonLayout')
2785+ self.orderingDownButton = QtGui.QPushButton(self.orderingButtonsWidget)
2786+ self.orderingDownButton.setObjectName(u'orderingDownButton')
2787+ self.orderingButtonLayout.addWidget(self.orderingDownButton)
2788+ self.orderingUpButton = QtGui.QPushButton(self.playerOrderGroupBox)
2789+ self.orderingUpButton.setObjectName(u'orderingUpButton')
2790+ self.orderingButtonLayout.addWidget(self.orderingUpButton)
2791+ self.playerOrderLayout.addWidget(self.orderingButtonsWidget)
2792+ self.leftLayout.addWidget(self.playerOrderGroupBox)
2793+ self.AdvancedGroupBox = QtGui.QGroupBox(self.leftColumn)
2794+ self.AdvancedGroupBox.setObjectName(u'AdvancedGroupBox')
2795+ self.AdvancedLayout = QtGui.QVBoxLayout(self.AdvancedGroupBox)
2796+ self.AdvancedLayout.setObjectName(u'AdvancedLayout')
2797+ self.OverridePlayerCheckBox = QtGui.QCheckBox(self.AdvancedGroupBox)
2798+ self.OverridePlayerCheckBox.setObjectName(u'OverridePlayerCheckBox')
2799+ self.AdvancedLayout.addWidget(self.OverridePlayerCheckBox)
2800+ self.leftLayout.addWidget(self.AdvancedGroupBox)
2801 self.leftLayout.addStretch()
2802 self.rightLayout.addStretch()
2803- QtCore.QObject.connect(self.usePhononCheckBox,
2804- QtCore.SIGNAL(u'stateChanged(int)'),
2805- self.onUsePhononCheckBoxChanged)
2806+ for key in self.media_players:
2807+ player = self.media_players[key]
2808+ checkbox = self.PlayerCheckBoxes[player.name]
2809+ QtCore.QObject.connect(checkbox,
2810+ QtCore.SIGNAL(u'stateChanged(int)'),
2811+ self.onPlayerCheckBoxChanged)
2812+ QtCore.QObject.connect(self.orderingUpButton,
2813+ QtCore.SIGNAL(u'pressed()'), self.onOrderingUpButtonPressed)
2814+ QtCore.QObject.connect(self.orderingDownButton,
2815+ QtCore.SIGNAL(u'pressed()'), self.onOrderingDownButtonPressed)
2816
2817 def retranslateUi(self):
2818- self.mediaModeGroupBox.setTitle(
2819- translate('MediaPlugin.MediaTab', 'Media Display'))
2820- self.usePhononCheckBox.setText(
2821- translate('MediaPlugin.MediaTab', 'Use Phonon for video playback'))
2822-
2823- def onUsePhononCheckBoxChanged(self, check_state):
2824- self.usePhonon = (check_state == QtCore.Qt.Checked)
2825- self.usePhononChanged = True
2826+ self.mediaPlayerGroupBox.setTitle(
2827+ translate('MediaPlugin.MediaTab', 'Available Media Players'))
2828+ for key in self.media_players:
2829+ player = self.media_players[key]
2830+ checkbox = self.PlayerCheckBoxes[player.name]
2831+ if player.available:
2832+ checkbox.setText(player.name)
2833+ else:
2834+ checkbox.setText(
2835+ unicode(translate('MediaPlugin.MediaTab',
2836+ '%s (unavailable)')) % player.name)
2837+ self.playerOrderGroupBox.setTitle(
2838+ translate('MediaPlugin.MediaTab', 'Player Order'))
2839+ self.orderingDownButton.setText(
2840+ translate('MediaPlugin.MediaTab', 'Down'))
2841+ self.orderingUpButton.setText(
2842+ translate('MediaPlugin.MediaTab', 'Up'))
2843+ self.AdvancedGroupBox.setTitle(UiStrings().Advanced)
2844+ self.OverridePlayerCheckBox.setText(
2845+ translate('MediaPlugin.MediaTab',
2846+ 'Allow media player to be overriden'))
2847+
2848+ def onPlayerCheckBoxChanged(self, check_state):
2849+ player = self.sender().text()
2850+ if check_state == QtCore.Qt.Checked:
2851+ if player not in self.usedPlayers:
2852+ self.usedPlayers.append(player)
2853+ else:
2854+ self.usedPlayers.takeAt(self.usedPlayers.indexOf(player))
2855+ self.updatePlayerList()
2856+
2857+ def updatePlayerList(self):
2858+ self.playerOrderlistWidget.clear()
2859+ for player in self.usedPlayers:
2860+ if player in self.PlayerCheckBoxes.keys():
2861+ if len(self.usedPlayers) == 1:
2862+ # at least one media player have to stay active
2863+ self.PlayerCheckBoxes[u'%s' % player].setEnabled(False)
2864+ else:
2865+ self.PlayerCheckBoxes[u'%s' % player].setEnabled(True)
2866+ self.playerOrderlistWidget.addItem(player)
2867+
2868+ def onOrderingUpButtonPressed(self):
2869+ currentRow = self.playerOrderlistWidget.currentRow()
2870+ if currentRow > 0:
2871+ item = self.playerOrderlistWidget.takeItem(currentRow)
2872+ self.playerOrderlistWidget.insertItem(currentRow - 1, item)
2873+ self.playerOrderlistWidget.setCurrentRow(currentRow - 1)
2874+ self.usedPlayers.move(currentRow, currentRow - 1)
2875+
2876+ def onOrderingDownButtonPressed(self):
2877+ currentRow = self.playerOrderlistWidget.currentRow()
2878+ if currentRow < self.playerOrderlistWidget.count() - 1:
2879+ item = self.playerOrderlistWidget.takeItem(currentRow)
2880+ self.playerOrderlistWidget.insertItem(currentRow + 1, item)
2881+ self.playerOrderlistWidget.setCurrentRow(currentRow + 1)
2882+ self.usedPlayers.move(currentRow, currentRow + 1)
2883
2884 def load(self):
2885- self.usePhonon = QtCore.QSettings().value(
2886- self.settingsSection + u'/use phonon',
2887- QtCore.QVariant(True)).toBool()
2888- self.usePhononCheckBox.setChecked(self.usePhonon)
2889+ self.usedPlayers = QtCore.QSettings().value(
2890+ self.settingsSection + u'/players',
2891+ QtCore.QVariant(u'webkit')).toString().split(u',')
2892+ for key in self.media_players:
2893+ player = self.media_players[key]
2894+ checkbox = self.PlayerCheckBoxes[player.name]
2895+ if player.available and player.name in self.usedPlayers:
2896+ checkbox.setChecked(True)
2897+ self.updatePlayerList()
2898+ self.OverridePlayerCheckBox.setChecked(QtCore.QSettings().value(
2899+ self.settingsSection + u'/override player',
2900+ QtCore.QVariant(QtCore.Qt.Unchecked)).toInt()[0])
2901
2902 def save(self):
2903- oldUsePhonon = QtCore.QSettings().value(
2904- u'media/use phonon', QtCore.QVariant(True)).toBool()
2905- if oldUsePhonon != self.usePhonon:
2906- QtCore.QSettings().setValue(self.settingsSection + u'/use phonon',
2907- QtCore.QVariant(self.usePhonon))
2908+ override_changed = False
2909+ player_string_changed = False
2910+ oldPlayerString = QtCore.QSettings().value(
2911+ self.settingsSection + u'/players',
2912+ QtCore.QVariant(u'webkit')).toString()
2913+ newPlayerString = self.usedPlayers.join(u',')
2914+ if oldPlayerString != newPlayerString:
2915+ # clean old Media stuff
2916+ QtCore.QSettings().setValue(self.settingsSection + u'/players',
2917+ QtCore.QVariant(newPlayerString))
2918+ player_string_changed = True
2919+ override_changed = True
2920+ setting_key = self.settingsSection + u'/override player'
2921+ if QtCore.QSettings().value(setting_key) != \
2922+ self.OverridePlayerCheckBox.checkState():
2923+ QtCore.QSettings().setValue(setting_key,
2924+ QtCore.QVariant(self.OverridePlayerCheckBox.checkState()))
2925+ override_changed = True
2926+ if override_changed:
2927+ Receiver.send_message(u'mediaitem_media_rebuild')
2928+ if player_string_changed:
2929+ Receiver.send_message(u'mediaitem_media_rebuild')
2930 Receiver.send_message(u'config_screen_changed')
2931
2932=== modified file 'openlp/plugins/media/mediaplugin.py'
2933--- openlp/plugins/media/mediaplugin.py 2011-07-23 21:29:24 +0000
2934+++ openlp/plugins/media/mediaplugin.py 2011-12-01 18:30:31 +0000
2935@@ -26,9 +26,7 @@
2936 ###############################################################################
2937
2938 import logging
2939-import mimetypes
2940-
2941-from PyQt4.phonon import Phonon
2942+import os
2943
2944 from openlp.core.lib import Plugin, StringContent, build_icon, translate
2945 from openlp.plugins.media.lib import MediaMediaItem, MediaTab
2946@@ -40,57 +38,28 @@
2947
2948 def __init__(self, plugin_helpers):
2949 Plugin.__init__(self, u'media', plugin_helpers,
2950- MediaMediaItem, MediaTab)
2951+ MediaMediaItem)
2952 self.weight = -6
2953 self.icon_path = u':/plugins/plugin_media.png'
2954 self.icon = build_icon(self.icon_path)
2955 # passed with drag and drop messages
2956 self.dnd_id = u'Media'
2957- self.additional_extensions = {
2958- u'audio/ac3': [u'.ac3'],
2959- u'audio/flac': [u'.flac'],
2960- u'audio/x-m4a': [u'.m4a'],
2961- u'audio/midi': [u'.mid', u'.midi'],
2962- u'audio/x-mp3': [u'.mp3'],
2963- u'audio/mpeg': [u'.mp3', u'.mp2', u'.mpga', u'.mpega', u'.m4a'],
2964- u'audio/qcelp': [u'.qcp'],
2965- u'audio/x-wma': [u'.wma'],
2966- u'audio/x-ms-wma': [u'.wma'],
2967- u'video/x-flv': [u'.flv'],
2968- u'video/x-matroska': [u'.mpv', u'.mkv'],
2969- u'video/x-wmv': [u'.wmv'],
2970- u'video/x-ms-wmv': [u'.wmv']}
2971- self.audio_extensions_list = []
2972- self.video_extensions_list = []
2973- mimetypes.init()
2974- for mimetype in Phonon.BackendCapabilities.availableMimeTypes():
2975- mimetype = unicode(mimetype)
2976- if mimetype.startswith(u'audio/'):
2977- self._addToList(self.audio_extensions_list, mimetype)
2978- elif mimetype.startswith(u'video/'):
2979- self._addToList(self.video_extensions_list, mimetype)
2980+ self.audio_extensions_list = \
2981+ self.mediaController.get_audio_extensions_list()
2982+ for ext in self.audio_extensions_list:
2983+ self.serviceManager.supportedSuffixes(ext[2:])
2984+ self.video_extensions_list = \
2985+ self.mediaController.get_video_extensions_list()
2986+ for ext in self.video_extensions_list:
2987+ self.serviceManager.supportedSuffixes(ext[2:])
2988
2989- def _addToList(self, list, mimetype):
2990- # Add all extensions which mimetypes provides us for supported types.
2991- extensions = mimetypes.guess_all_extensions(unicode(mimetype))
2992- for extension in extensions:
2993- ext = u'*%s' % extension
2994- if ext not in list:
2995- list.append(ext)
2996- self.serviceManager.supportedSuffixes(extension[1:])
2997- log.info(u'MediaPlugin: %s extensions: %s' % (mimetype,
2998- u' '.join(extensions)))
2999- # Add extensions for this mimetype from self.additional_extensions.
3000- # This hack clears mimetypes' and operating system's shortcomings
3001- # by providing possibly missing extensions.
3002- if mimetype in self.additional_extensions.keys():
3003- for extension in self.additional_extensions[mimetype]:
3004- ext = u'*%s' % extension
3005- if ext not in list:
3006- list.append(ext)
3007- self.serviceManager.supportedSuffixes(extension[1:])
3008- log.info(u'MediaPlugin: %s additional extensions: %s' % (mimetype,
3009- u' '.join(self.additional_extensions[mimetype])))
3010+ def getSettingsTab(self, parent):
3011+ """
3012+ Create the settings Tab
3013+ """
3014+ visible_name = self.getString(StringContent.VisibleName)
3015+ return MediaTab(parent, self.name, visible_name[u'title'],
3016+ self.mediaController.mediaPlayers, self.icon_path)
3017
3018 def about(self):
3019 about_text = translate('MediaPlugin', '<strong>Media Plugin</strong>'
3020@@ -123,3 +92,29 @@
3021 'Add the selected media to the service.')
3022 }
3023 self.setPluginUiTextStrings(tooltips)
3024+
3025+ def finalise(self):
3026+ """
3027+ Time to tidy up on exit
3028+ """
3029+ log.info(u'Media Finalising')
3030+ self.mediaController.finalise()
3031+ Plugin.finalise(self)
3032+
3033+ def getDisplayCss(self):
3034+ """
3035+ Add css style sheets to htmlbuilder
3036+ """
3037+ return self.mediaController.get_media_display_css()
3038+
3039+ def getDisplayJavaScript(self):
3040+ """
3041+ Add javascript functions to htmlbuilder
3042+ """
3043+ return self.mediaController.get_media_display_javascript()
3044+
3045+ def getDisplayHtml(self):
3046+ """
3047+ Add html code to htmlbuilder
3048+ """
3049+ return self.mediaController.get_media_display_html()
3050
3051=== removed file 'resources/forms/mediafilesdialog.ui'
3052--- resources/forms/mediafilesdialog.ui 2011-08-23 21:48:46 +0000
3053+++ resources/forms/mediafilesdialog.ui 1970-01-01 00:00:00 +0000
3054@@ -1,95 +0,0 @@
3055-<?xml version="1.0" encoding="UTF-8"?>
3056-<ui version="4.0">
3057- <class>MediaFilesDialog</class>
3058- <widget class="QDialog" name="MediaFilesDialog">
3059- <property name="windowModality">
3060- <enum>Qt::ApplicationModal</enum>
3061- </property>
3062- <property name="geometry">
3063- <rect>
3064- <x>0</x>
3065- <y>0</y>
3066- <width>400</width>
3067- <height>300</height>
3068- </rect>
3069- </property>
3070- <property name="windowTitle">
3071- <string>Select Media File(s)</string>
3072- </property>
3073- <property name="modal">
3074- <bool>true</bool>
3075- </property>
3076- <layout class="QVBoxLayout" name="filesVerticalLayout">
3077- <property name="spacing">
3078- <number>8</number>
3079- </property>
3080- <property name="margin">
3081- <number>8</number>
3082- </property>
3083- <item>
3084- <widget class="QLabel" name="selectLabel">
3085- <property name="text">
3086- <string>Select one or more audio files from the list below, and click OK to import them into this song.</string>
3087- </property>
3088- <property name="wordWrap">
3089- <bool>true</bool>
3090- </property>
3091- </widget>
3092- </item>
3093- <item>
3094- <widget class="QListView" name="fileListView">
3095- <property name="alternatingRowColors">
3096- <bool>true</bool>
3097- </property>
3098- </widget>
3099- </item>
3100- <item>
3101- <widget class="QDialogButtonBox" name="buttonBox">
3102- <property name="orientation">
3103- <enum>Qt::Horizontal</enum>
3104- </property>
3105- <property name="standardButtons">
3106- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
3107- </property>
3108- </widget>
3109- </item>
3110- </layout>
3111- </widget>
3112- <resources>
3113- <include location="../images/openlp-2.qrc"/>
3114- </resources>
3115- <connections>
3116- <connection>
3117- <sender>buttonBox</sender>
3118- <signal>accepted()</signal>
3119- <receiver>MediaFilesDialog</receiver>
3120- <slot>accept()</slot>
3121- <hints>
3122- <hint type="sourcelabel">
3123- <x>248</x>
3124- <y>254</y>
3125- </hint>
3126- <hint type="destinationlabel">
3127- <x>157</x>
3128- <y>274</y>
3129- </hint>
3130- </hints>
3131- </connection>
3132- <connection>
3133- <sender>buttonBox</sender>
3134- <signal>rejected()</signal>
3135- <receiver>MediaFilesDialog</receiver>
3136- <slot>reject()</slot>
3137- <hints>
3138- <hint type="sourcelabel">
3139- <x>316</x>
3140- <y>260</y>
3141- </hint>
3142- <hint type="destinationlabel">
3143- <x>286</x>
3144- <y>274</y>
3145- </hint>
3146- </hints>
3147- </connection>
3148- </connections>
3149-</ui>
3150
3151=== added file 'resources/pyinstaller/hook-openlp.core.ui.media.py'
3152--- resources/pyinstaller/hook-openlp.core.ui.media.py 1970-01-01 00:00:00 +0000
3153+++ resources/pyinstaller/hook-openlp.core.ui.media.py 2011-12-01 18:30:31 +0000
3154@@ -0,0 +1,30 @@
3155+# -*- coding: utf-8 -*-
3156+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
3157+
3158+###############################################################################
3159+# OpenLP - Open Source Lyrics Projection #
3160+# --------------------------------------------------------------------------- #
3161+# Copyright (c) 2008-2011 Raoul Snyman #
3162+# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael #
3163+# Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, #
3164+# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, #
3165+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode #
3166+# Woldsund #
3167+# --------------------------------------------------------------------------- #
3168+# This program is free software; you can redistribute it and/or modify it #
3169+# under the terms of the GNU General Public License as published by the Free #
3170+# Software Foundation; version 2 of the License. #
3171+# #
3172+# This program is distributed in the hope that it will be useful, but WITHOUT #
3173+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
3174+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
3175+# more details. #
3176+# #
3177+# You should have received a copy of the GNU General Public License along #
3178+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
3179+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
3180+###############################################################################
3181+
3182+hiddenimports = ['openlp.core.ui.media.phononplayer',
3183+ 'openlp.core.ui.media.vlcplayer',
3184+ 'openlp.core.ui.media.webkitplayer']
3185
3186=== modified file 'scripts/windows-builder.py'
3187--- scripts/windows-builder.py 2011-10-28 13:28:57 +0000
3188+++ scripts/windows-builder.py 2011-12-01 18:30:31 +0000
3189@@ -233,6 +233,19 @@
3190 copy(os.path.join(root, filename),
3191 os.path.join(dest_path, filename))
3192
3193+def copy_media_player():
3194+ print u'Copying media player...'
3195+ source = os.path.join(source_path, u'core', u'ui', u'media')
3196+ dest = os.path.join(dist_path, u'core', u'ui', u'media')
3197+ for root, dirs, files in os.walk(source):
3198+ for filename in files:
3199+ if not filename.endswith(u'.pyc'):
3200+ dest_path = os.path.join(dest, root[len(source)+1:])
3201+ if not os.path.exists(dest_path):
3202+ os.makedirs(dest_path)
3203+ copy(os.path.join(root, filename),
3204+ os.path.join(dest_path, filename))
3205+
3206 def copy_windows_files():
3207 print u'Copying extra files for Windows...'
3208 copy(os.path.join(winres_path, u'OpenLP.ico'),
3209@@ -355,6 +368,7 @@
3210 run_pyinstaller()
3211 write_version_file()
3212 copy_plugins()
3213+ copy_media_player()
3214 if os.path.exists(manual_path):
3215 run_sphinx()
3216 run_htmlhelp()