From 431a51067d83801231daf9bdd8bd020e8ea13dda Mon Sep 17 00:00:00 2001 From: Roman Moravcik Date: Mon, 18 Jan 2010 09:37:12 +0100 Subject: [PATCH] Imported mafw-gst-renderer_0.1.2009.47-1+0m5 --- AUTHORS | 10 + COPYING | 504 +++ Makefile.am | 41 + acinclude.m4 | 179 + autogen.sh | 9 + configure.ac | 222 ++ debian/changelog | 1114 ++++++ debian/compat | 1 + debian/control | 28 + debian/copyright | 6 + debian/mafw-gst-renderer.install.in | 1 + debian/mafw-gst-renderer.postinst | 6 + debian/mafw-gst-renderer.prerm | 6 + debian/rules | 96 + libmafw-gst-renderer/Makefile.am | 55 + libmafw-gst-renderer/blanking.c | 119 + libmafw-gst-renderer/blanking.h | 37 + libmafw-gst-renderer/gstscreenshot.c | 259 ++ libmafw-gst-renderer/gstscreenshot.h | 36 + .../mafw-gst-renderer-marshal.list | 2 + .../mafw-gst-renderer-state-paused.c | 379 ++ .../mafw-gst-renderer-state-paused.h | 76 + .../mafw-gst-renderer-state-playing.c | 445 +++ .../mafw-gst-renderer-state-playing.h | 76 + .../mafw-gst-renderer-state-stopped.c | 319 ++ .../mafw-gst-renderer-state-stopped.h | 76 + .../mafw-gst-renderer-state-transitioning.c | 414 ++ .../mafw-gst-renderer-state-transitioning.h | 82 + libmafw-gst-renderer/mafw-gst-renderer-state.c | 825 ++++ libmafw-gst-renderer/mafw-gst-renderer-state.h | 239 ++ libmafw-gst-renderer/mafw-gst-renderer-utils.c | 105 + libmafw-gst-renderer/mafw-gst-renderer-utils.h | 34 + .../mafw-gst-renderer-worker-volume.c | 702 ++++ .../mafw-gst-renderer-worker-volume.h | 64 + libmafw-gst-renderer/mafw-gst-renderer-worker.c | 2373 +++++++++++ libmafw-gst-renderer/mafw-gst-renderer-worker.h | 210 + libmafw-gst-renderer/mafw-gst-renderer.c | 2191 ++++++++++ libmafw-gst-renderer/mafw-gst-renderer.h | 293 ++ libmafw-gst-renderer/mafw-playlist-iterator.c | 521 +++ libmafw-gst-renderer/mafw-playlist-iterator.h | 95 + mafw-gst-renderer-uninstalled.pc.in | 11 + tests/Makefile.am | 60 + tests/check-mafw-gst-renderer.c | 4174 ++++++++++++++++++++ tests/check-main.c | 53 + tests/mafw-mock-playlist.c | 321 ++ tests/mafw-mock-playlist.h | 84 + tests/mafw-mock-pulseaudio.c | 295 ++ tests/mafw-mock-pulseaudio.h | 33 + tests/mafw-test-player.c | 312 ++ tests/media/test.avi | Bin 0 -> 86540 bytes tests/media/test.wav | Bin 0 -> 441484 bytes tests/media/testframe.png | Bin 0 -> 13945 bytes tests/test.suppressions | 864 ++++ 53 files changed, 18457 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 acinclude.m4 create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/mafw-gst-renderer.install.in create mode 100644 debian/mafw-gst-renderer.postinst create mode 100644 debian/mafw-gst-renderer.prerm create mode 100755 debian/rules create mode 100644 libmafw-gst-renderer/Makefile.am create mode 100644 libmafw-gst-renderer/blanking.c create mode 100644 libmafw-gst-renderer/blanking.h create mode 100644 libmafw-gst-renderer/gstscreenshot.c create mode 100644 libmafw-gst-renderer/gstscreenshot.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-marshal.list create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-paused.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-paused.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-playing.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-playing.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-stopped.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-stopped.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-state.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-utils.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-utils.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-worker-volume.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-worker-volume.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-worker.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer-worker.h create mode 100644 libmafw-gst-renderer/mafw-gst-renderer.c create mode 100644 libmafw-gst-renderer/mafw-gst-renderer.h create mode 100644 libmafw-gst-renderer/mafw-playlist-iterator.c create mode 100644 libmafw-gst-renderer/mafw-playlist-iterator.h create mode 100644 mafw-gst-renderer-uninstalled.pc.in create mode 100644 tests/Makefile.am create mode 100644 tests/check-mafw-gst-renderer.c create mode 100644 tests/check-main.c create mode 100644 tests/mafw-mock-playlist.c create mode 100644 tests/mafw-mock-playlist.h create mode 100644 tests/mafw-mock-pulseaudio.c create mode 100644 tests/mafw-mock-pulseaudio.h create mode 100644 tests/mafw-test-player.c create mode 100644 tests/media/test.avi create mode 100644 tests/media/test.wav create mode 100644 tests/media/testframe.png create mode 100644 tests/test.suppressions delete mode 100644 welcome diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..a2c188d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,10 @@ +Visa Smolander +Zeeshan Ali +Mikael Saarenpää +Antía Puentes Felpeto +Iago Toral Quiroga +Juan Suárez Romero +Sandor Pinter +Xabier Rodríguez Calvar +Juha Kellokoski +Mika Tapojarvi diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5ab7695 --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6ab6aea --- /dev/null +++ b/Makefile.am @@ -0,0 +1,41 @@ +# +# Makefile.am for MAFW gst renderer library. +# +# Author: Visa Smolander +# +# Copyright (C) 2007, 2008, 2009 Nokia. All rights reserved. + + +SUBDIRS = libmafw-gst-renderer + +if ENABLE_TESTS +SUBDIRS += tests +endif + +noinst_DATA = mafw-gst-renderer-uninstalled.pc +EXTRA_DIST = mafw-gst-renderer-uninstalled.pc.in + +# Extra clean files so that maintainer-clean removes *everything* +MAINTAINERCLEANFILES = aclocal.m4 compile config.guess \ + config.sub configure depcomp \ + install-sh ltmain.sh Makefile.in \ + missing config.h.in *-stamp omf.make + +if ENABLE_COVERAGE +LCOV_DATA_DIR = lcov-data +LCOV_DATA_FILE = lcov.info + +distclean-local: + -rm -rf $(LCOV_DATA_DIR) + +lcov-zero-counters: + $(LCOV) -z -d . + +lcov: + -mkdir -p $(LCOV_DATA_DIR) + $(LCOV) -c -d . -o $(LCOV_DATA_DIR)/$(LCOV_DATA_FILE) + genhtml -s $(LCOV_DATA_DIR)/$(LCOV_DATA_FILE) -o $(LCOV_DATA_DIR) + @echo + @echo "Please, have a look on ./$(LCOV_DATA_DIR)/index.html for coverage statistics" + @echo +endif diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..1457d2a --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,179 @@ +dnl AM_PATH_CHECK([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for check, and define CHECK_CFLAGS and CHECK_LIBS +dnl + +AC_DEFUN([AM_PATH_CHECK], +[ + AC_ARG_WITH(check, + [ --with-check=PATH prefix where check is installed [default=auto]]) + + min_check_version=ifelse([$1], ,0.8.2,$1) + + AC_MSG_CHECKING(for check - version >= $min_check_version) + + if test x$with_check = xno; then + AC_MSG_RESULT(disabled) + ifelse([$3], , AC_MSG_ERROR([disabling check is not supported]), [$3]) + else + if test "x$with_check" != x; then + CHECK_CFLAGS="-I$with_check/include" + CHECK_LIBS="-L$with_check/lib -lcheck" + else + CHECK_CFLAGS="" + CHECK_LIBS="-lcheck" + fi + + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + + CFLAGS="$CFLAGS $CHECK_CFLAGS" + LIBS="$CHECK_LIBS $LIBS" + + rm -f conf.check-test + AC_TRY_RUN([ +#include +#include + +#include + +int main () +{ + int major, minor, micro; + char *tmp_version; + + system ("touch conf.check-test"); + + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = strdup("$min_check_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string\n", "$min_check_version"); + return 1; + } + + if ((CHECK_MAJOR_VERSION != check_major_version) || + (CHECK_MINOR_VERSION != check_minor_version) || + (CHECK_MICRO_VERSION != check_micro_version)) + { + printf("\n*** The check header file (version %d.%d.%d) does not match\n", + CHECK_MAJOR_VERSION, CHECK_MINOR_VERSION, CHECK_MICRO_VERSION); + printf("*** the check library (version %d.%d.%d).\n", + check_major_version, check_minor_version, check_micro_version); + return 1; + } + + if ((check_major_version > major) || + ((check_major_version == major) && (check_minor_version > minor)) || + ((check_major_version == major) && (check_minor_version == minor) && (check_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** An old version of check (%d.%d.%d) was found.\n", + check_major_version, check_minor_version, check_micro_version); + printf("*** You need a version of check being at least %d.%d.%d.\n", major, minor, micro); + printf("***\n"); + printf("*** If you have already installed a sufficiently new version, this error\n"); + printf("*** probably means that the wrong copy of the check library and header\n"); + printf("*** file is being found. Rerun configure with the --with-check=PATH option\n"); + printf("*** to specify the prefix where the correct version was installed.\n"); + } + + return 1; +} +],, no_check=yes, [echo $ac_n "cross compiling; assumed OK... $ac_c"]) + + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + + if test "x$no_check" = x ; then + AC_MSG_RESULT(yes) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test -f conf.check-test ; then + : + else + echo "*** Could not run check test program, checking why..." + CFLAGS="$CFLAGS $CHECK_CFLAGS" + LIBS="$CHECK_LIBS $LIBS" + AC_TRY_LINK([ +#include +#include + +#include +], , [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding check. You'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for" + echo "*** the exact error that occured." ]) + + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + CHECK_CFLAGS="" + CHECK_LIBS="" + + rm -f conf.check-test + ifelse([$3], , AC_MSG_ERROR([check not found]), [$3]) + fi + + AC_SUBST(CHECK_CFLAGS) + AC_SUBST(CHECK_LIBS) + + rm -f conf.check-test + + fi +]) + +dnl AM_MAFW_PLUGIN_CHECK(PLUGIN, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test to check whether a plugin exists +dnl + +AC_DEFUN([AM_MAFW_PLUGIN_CHECK], +[ + AC_MSG_CHECKING(for MAFW plugin) + + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + ac_save_CC="$CC" + + CFLAGS="$CFLAGS $(pkg-config mafw --cflags)" + LIBS="$LIBS $(pkg-config mafw --libs)" + CC="libtool --mode=link gcc" + + AC_TRY_RUN([ +#include + +int main () +{ + MafwRegistry *registry = NULL; + gboolean result; + + g_type_init(); + + registry = MAFW_REGISTRY(mafw_get_local_registry()); + if (!registry) { + printf ("Cannot get registry\n"); + return 1; + } + result = mafw_local_registry_load_plugin(MAFW_LOCAL_REGISTRY(registry), $1, NULL); + if (!result) { + printf ("Unable to load %s\n", $1); + return 1; + } else { + return 0; + } +} + ], [$2], [$3]) + + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + CC="$ac_save_CC" +]) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..d88de29 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +export AUTOMAKE="automake-1.9" +export ACLOCAL=`echo $AUTOMAKE | sed s/automake/aclocal/` + +autoreconf -v -f -i || exit 1 +test -n "$NOCONFIGURE" || ./configure \ + --enable-debug --enable-maintainer-mode "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9b146ec --- /dev/null +++ b/configure.ac @@ -0,0 +1,222 @@ +# +# configure.ac for MAFW gstreamer renderer library +# +# Author: Visa Smolander +# +# Copyright (C) 2007, 2008, 2009 Nokia. All rights reserved. + +AC_PREREQ([2.53]) +AC_INIT([mafw-gst-renderer], [0.1.2009.47-1]) + +AC_CONFIG_SRCDIR([libmafw-gst-renderer/mafw-gst-renderer.h]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_AUX_DIR([build-aux]) + +AM_INIT_AUTOMAKE([foreign tar-ustar]) +AM_MAINTAINER_MODE + +AC_DISABLE_STATIC + +dnl Prevent AC_PROG_CC adding '-g -O2' to CFLAGS. +SAVEDCFLAGS="$CFLAGS" +AC_PROG_CC +if test "x$GCC" = xyes; then + CFLAGS="$SAVEDCFLAGS" +fi + +AC_PROG_LIBTOOL +AC_PROG_INSTALL + +# DISABLED_BY_DEFAULT(NAME, DESCRIPTION) +# --------------------------------- +# Creates a new --enable-* option, with default value `no'. +AC_DEFUN([DISABLED_BY_DEFAULT], [dnl + AC_ARG_ENABLE([$1], AS_HELP_STRING([--enable-$1], [$2]), [],[dnl + m4_bpatsubst([enable_$1], [[^0-9a-z]], [_])=no])dnl +])# DISABLED_BY_DEFAULT + +# ENABLED_BY_DEFAULT(NAME, DESCRIPTION) +# --------------------------------- +# Creates a new --disable-* option, with default value `yes'. +AC_DEFUN([ENABLED_BY_DEFAULT], [dnl + AC_ARG_ENABLE([$1], AS_HELP_STRING([--disable-$1], [$2]), [],[dnl + m4_bpatsubst([enable_$1], [[^0-9a-z]], [_])=yes])dnl +])# ENABLED_BY_DEFAULT + +dnl Prerequisites. + +GSTREAMER_VERSION=0.10.20 + +AM_PATH_GLIB_2_0(2.15.0, [], [], [glib]) +PKG_CHECK_MODULES(DEPS, + gobject-2.0 >= 2.0 + gstreamer-0.10 >= $GSTREAMER_VERSION + gstreamer-plugins-base-0.10 >= $GSTREAMER_VERSION + mafw >= 0.1 + libosso >= 2.0 + x11 + hal + totem-plparser + gconf-2.0 >= 2.0 + gnome-vfs-2.0 +) + +dnl Check for GdkPixbuf, needed for dumping current frame +GDKPIXBUF_REQUIRED=2.12.0 +AC_ARG_ENABLE(gdkpixbuf, + AS_HELP_STRING([--disable-gdkpixbuf], + [Disable GdkPixbuf support, required for current frame dumping]),, + [enable_gdkpixbuf=auto]) + +if test "x$enable_gdkpixbuf" != "xno"; then + PKG_CHECK_MODULES(GDKPIXBUF, + [gdk-pixbuf-2.0 >= $GDKPIXBUF_REQUIRED], + [have_gdkpixbuf=yes], + [have_gdkpixbuf=no]) + AC_SUBST(GDKPIXBUF_LIBS) + AC_SUBST(GDKPIXBUF_CFLAGS) + + if test "x$have_gdkpixbuf" = "xyes"; then + AC_DEFINE(HAVE_GDKPIXBUF, [], [Define if we have GdkPixbuf]) + fi +else + have_gdkpixbuf="no (disabled)" +fi + +if test "x$enable_gdkpixbuf" = "xyes"; then + if test "x$have_gdkpixbuf" != "xyes"; then + AC_MSG_ERROR([Couldn't find GdkPixbuf >= $GDKPIXBUF_REQUIRED.]) + fi +fi + +AM_CONDITIONAL(HAVE_GDKPIXBUF, test "x$have_gdkpixbuf" = "xyes") + + +dnl Check for Conic, needed connection error handling +CONIC_REQUIRED=0.16 +AC_ARG_ENABLE(conic, + AS_HELP_STRING([--disable-conic], + [Disable Conic support, required to handle network errors]),, + [enable_conic=auto]) + +if test "x$enable_conic" != "xno"; then + PKG_CHECK_MODULES(CONIC, + [conic >= $CONIC_REQUIRED], + [have_conic=yes], + [have_conic=no]) + AC_SUBST(CONIC_LIBS) + AC_SUBST(CONIC_CFLAGS) + + if test "x$have_conic" = "xyes"; then + AC_DEFINE(HAVE_CONIC, [], [Define if we have Conic]) + fi +else + have_conic="no (disabled)" +fi + +if test "x$enable_conic" = "xyes"; then + if test "x$have_conic" != "xyes"; then + AC_MSG_ERROR([Couldn't find Conic >= $CONIC_REQUIRED.]) + fi +fi + +AM_CONDITIONAL(HAVE_CONIC, test "x$have_conic" = "xyes") + + + +plugindir=`$PKG_CONFIG --variable=plugindir mafw` +AC_SUBST(plugindir) + +dnl Default compile flags. (NOTE: CFLAGS is reserved for the user!) + +AC_SUBST([_CFLAGS]) +AC_SUBST([_LDFLAGS]) +_CFLAGS="-Wall -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations" +_CFLAGS="$_CFLAGS -ggdb3" + +dnl Configure-time options. + +dnl Debugging. +DISABLED_BY_DEFAULT([debug], [compile with debug flags and extra output]) +if test "x$enable_debug" = xyes; then + AC_DEFINE([MAFW_DEBUG], [1], [Enable debugging related parts.]) + _CFLAGS="$_CFLAGS -O0 -Werror -DGTK_DISABLE_DEPRECATED" +else + AC_DEFINE([G_DEBUG_DISABLE], [1], [Disable g_debug() calls.]) + _CFLAGS="$_CFLAGS -O2" +fi +AS_IF([test "x$enable_debug" = xyes], + [AC_DEFINE([MAFW_DEBUG], [1], [Enable extra debug messages]) + CFLAGS="$CFLAGS -Werror -O0 -ggdb3 -DGTK_DISABLE_DEPRECATED"], + [AC_DEFINE([G_DEBUG_DISABLE], [1], [Disable g_debug calls]) + CFLAGS="$CFLAGS -O2"]) + + +dnl Tests. +DISABLED_BY_DEFAULT([tests], [disable unit tests]) +if test "x${SBOX_DPKG_INST_ARCH}" = "xarmel"; then + AC_MSG_WARN([Tests are disabled for compilation in armel]) + enable_tests="no" +fi +if test "x$enable_tests" = xyes; then + PKG_CHECK_MODULES(CHECKMORE, [checkmore, check >= 0.9.4]) + if test -z "$CHECKMORE_LIBS"; then + AC_MSG_WARN([checkmore is needed for unit tests!]) + fi +fi +AM_CONDITIONAL(ENABLE_TESTS, + [test "x$enable_tests" = xyes && test -n "$CHECKMORE_LIBS"]) + +dnl Volume handling +if test "x${SBOX_DPKG_INST_ARCH}" = "xi386"; then + DISABLED_BY_DEFAULT([pulse-volume], [enable volume handling with pulse]) +else + ENABLED_BY_DEFAULT([pulse-volume], [enable volume handling with pulse]) +fi +if test "x$enable_pulse_volume" = xno; then + AC_DEFINE([MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME], [1], [Disables volume handling with pulse.]) +else + PKG_CHECK_MODULES(VOLUME, libpulse-mainloop-glib >= 0.9.15) +fi + + +dnl Mute +DISABLED_BY_DEFAULT([mute], [enable mute handling]) +if test "x$enable_mute" = xyes; then + AC_DEFINE([MAFW_GST_RENDERER_ENABLE_MUTE], [1], [Enable mute.]) +fi + +dnl Tracing. +DISABLED_BY_DEFAULT([tracing], [enable function instrumentation (tracing)]) +if test "x$enable_tracing" = xyes; then + _CFLAGS="$_CFLAGS -finstrument-functions -rdynamic" +fi + +dnl Coverage. +DISABLED_BY_DEFAULT([coverage], [enable coverage data generation (gcov)]) +if test "x$enable_coverage" = xyes; then + AC_PATH_PROG(LCOV, [lcov], [lcov]) + if test "x$LCOV" = x; then + echo You need to install lcov to get actual reports! + echo See http://ltp.sf.net/coverage/lcov.php + fi + if test "x$SBOX_USE_CCACHE" == xyes; then + AC_MSG_ERROR([Please set SBOX_USE_CCACHE=no to use coverage.]) + fi + _CFLAGS="$_CFLAGS -fprofile-arcs -ftest-coverage" + _LDFLAGS="$_LDFLAGS -g -lgcov" +fi +AM_CONDITIONAL(ENABLE_COVERAGE, + [test "x$enable_coverage" != xno && test -n "$LCOV"]) + +dnl Output files. + +AC_CONFIG_FILES([ + Makefile + mafw-gst-renderer-uninstalled.pc + libmafw-gst-renderer/Makefile + tests/Makefile + debian/mafw-gst-renderer.install +]) + +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..9517254 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,1114 @@ +mafw-gst-renderer (0.1.2009.47-1+0m5) unstable; urgency=low + + * This entry has been added by BIFH queue processor + Suffix +0m5 added to package revision + + -- mika tapojarvi Thu, 26 Nov 2009 11:40:45 +0200 + +mafw-gst-renderer (0.1.2009.47-1) unstable; urgency=low + + * Fixes: NB#141508 - Specific video file (mjpeg) makes Mediaplayer unusable + + -- Mika Tapojärvi Fri, 20 Nov 2009 04:18:15 +0200 + +mafw-gst-renderer (0.1.2009.44-1) unstable; urgency=low + + * Fixes: NB#143299 - mafw_renderer_get_current_metadata don't give correct duration. + + -- Mika Tapojärvi Wed, 28 Oct 2009 23:48:22 +0200 + +mafw-gst-renderer (0.1.2009.42-2) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Tue, 20 Oct 2009 13:26:36 +0300 + +mafw-gst-renderer (0.1.2009.42-1) unstable; urgency=low + + * Version and changelog updated for pre-release 0.2009.42-1 + * Rebuild needed. + + -- Mika Tapojärvi Mon, 12 Oct 2009 17:21:48 +0300 + +mafw-gst-renderer (0.1.2009.40-1) unstable; urgency=low + + * Version and changelog updated for pre-release 0.2009.40-1 + * PR_1_1_baseline copied from trunk. + + -- Mika Tapojärvi Sun, 04 Oct 2009 15:47:32 +0300 + +mafw-gst-renderer (0.1.2009.39-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.39-1 + * Check for changes only in device having the video-out property. + * Disabled test of pulse reconnection as it is not needed with fake volume + manager to fix volume test + * Changed default fake volume initialization to 0.485 instead of 1 to fix + volume tests + * Added return of initialized fake volume manager in an idle call to fix + volume tests + * Added function to reset volume to pulse pipeline in case pulse volume + management is disabled + * Added fake volume manager and disabled normal handling with pulse when + pulse volume is disabled in compilation. + * We avoid setting audiosink to the pipeline and native flags when pulse + volume is disabled. + * Moved checking for pulse dependency to its conditional compilation + * Added support for conditional compilation regarding to volume in + renderer + * Adds a macro to round values when converting from nanoseconds to seconds. + + -- Juha Kellokoski Fri, 18 Sep 2009 14:21:32 +0300 + +mafw-gst-renderer (0.1.2009.37-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.37-1 + * Fixes: NB#121136 - [Power Management]Device never goto power saving mode when video playback is connected to TV out + * Fixes: NB#134730 - [PR1.1 proposal] mafw-gst-renderer.c + * Fixes: NB#134728 - [PR1.1 proposal] mafw-gst-renderer-worker.c + * Fixes: NB#134495 - [PR1.1 proposal] State changed signal does not come sometimes when stream is played + * if for some reason client starts a resume operation + after a seek on a stream and by the time we get the resume command we + have not started buffering yet, make sure we signal the state change + to playing to the client. + + -- Juha Kellokoski Fri, 04 Sep 2009 12:00:00 +0300 + +mafw-gst-renderer (0.1.2009.35-3) unstable; urgency=low + + * MAFW Sales RC4. + * Fixes: NB#129912 - Audio playback jarring while receiving a SMS while multiple browser/applications are open. + + -- Juha Kellokoski Tue, 25 Aug 2009 14:15:34 +0300 + +mafw-gst-renderer (0.1.2009.35-2) unstable; urgency=low + + * First MAFW Sales RC. + + -- Mika Tapojärvi Fri, 21 Aug 2009 16:38:27 +0300 + +mafw-gst-renderer (0.1.2009.35-1) unstable; urgency=low + + * Fixes: NB#129912 - Audio playback jarring while receiving a SMS while multiple browser/applications are open. + * Increased the priority of the mafw-gst-renderer. + + -- Mika Tapojärvi Thu, 20 Aug 2009 17:34:03 +0300 + +mafw-gst-renderer (0.1.2009.34-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.34-3 + * Fixes: NB#132950 - Video seeking is slow most of the time + * Check if we get a different duration that the source one and update it + if needed in renderer + * Added function mafw_gst_renderer_update_source_duration + * Reworked mafw_gst_renderer_increase_playcount to use _get_source function + * Reworked mafw_gst_renderer_get_metadata to use _get_source function + * Created _get_source function in renderer + * Reworked _check_duration condition into two different ifs and extracted + duration in seconds calculation + * Added duration to the source metadata request and kept it in the + renderer metadata structure. + * Duration in renderer converted in gint instead of guint to hold + negative values + * flag GST_SEEK_FLAG_KEY_UNIT. + * Patch provided by Rene Stadler. + + -- Mika Tapojärvi Mon, 17 Aug 2009 22:06:17 +0300 + +mafw-gst-renderer (0.1.2009.33-3) unstable; urgency=low + + * Fixes: NB#131655 - UPnP: Playback starts from the first instead of playing from where we paused if left the device idle + * Fixes: NB#131609 - Mafw-dbus-wrapper crashes. Audio cannot be heard from device's loudspeaker. + * Replaced assertion with critical in volume manager for unsuccessful + setting volume in pulse operations. + + -- Mika Tapojärvi Thu, 13 Aug 2009 17:03:10 +0300 + + +mafw-gst-renderer (0.1.2009.33-2) unstable; urgency=low + + * Fixes: NB#128110 - Playback neither stopped nor internal mmc is mounted onto pc when connected in mass storage mode + + -- Mika Tapojärvi Mon, 10 Aug 2009 12:27:43 +0300 + +mafw-gst-renderer (0.1.2009.33-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.33-1 + * Fixes: NB#128110 - Playback neither stopped nor internal mmc is mounted onto pc when connected in mass storage mode + * New Build-Dependency libosso-gnomevfs2-dev added. + + -- Mika Tapojärvi Wed, 05 Aug 2009 12:37:05 +0300 + +mafw-gst-renderer (0.1.2009.32-1) unstable; urgency=low + + * MAFW, pre-release 0.1.2009.32-1 + + -- Mika Tapojärvi Sun, 02 Aug 2009 22:32:27 +0300 + +mafw-gst-renderer (0.1.2009.30-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.30-3 + + -- Juha Kellokoski Wed, 22 Jul 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.30-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.30-2 + * Fixes: NB#128479 - Seeking in UPnP signals pause and keeps on playing + * Solved problem when pausing while buffering in renderer worker + * We activate state changes if we resume while buffering to report the + state change to playing in renderer worker + + -- Juha Kellokoski Wed, 22 Jul 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.30-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.30-1 + * Added more debug to the _handle_buffering function in renderer worker + * Just set pipeline to playing when finished buffering of a stream when + seeking to avoid signalling PAUSE. + * Added documentation in _handle_state_changed function in renderer worker + * Added comments in _do_play function in renderer worker + * Added comments in _finalize_startup funcion in renderer worker + * Reworked _handle_state_changed to use GST_STATE_TRANSITION in renderer + worker. + * Reworked _handle_state_changed to use _do_pause in renderer worker + * Created _do_pause function to notify the state change and get the + current frame on pause if needed in renderer worker. + * Removed buffering info from renderer as it was being handled by worker + * Removed seek in pause comments as we are not using them in renderer worker + * Simplified if condition in state managegement in renderer worker. + * Removed state assignment because it could be done at one point in + _handle_state_changed in renderer worker. + * Added comments in field names in renderer worker to clarify their use + + -- Juha Kellokoski Fri, 17 Jul 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.28-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.28-2 + * Ref the assigned playlist + + -- Juha Kellokoski Tue, 07 Jul 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.28-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.28-1 + * Fixes: NB#123689 - mafw-dbus-wrapper-76CE-6-4559.rcore.lzo crashed + * Ref the assigned playlist + * Removing an unnecessary comparison. + * Added g_debug in volume manager to indicate that it is initialized and + return the instance + * Changed _connect function to get an InitCbCallback in volume manager + * Modified _reconnect function in volume manager to receive an + InitCbCallback and propertly return the volume manager if reconnection + happens before having connected a first time + * Written function to create the InitCbClosure in volume manager + * When [re-]connection to pulse fails, we log a critical and reconnect + after 1 second + * Modified log in renderer tests to log only errors + * Added tests for pulseaudio reconnection in renderer + * Removed volume tests in playing since it is not needed anymore as it + does not depend on the playing state. + * Added check for receiving mute when property is changed in renderer + tests + * In renderer tests, wait for the volume manager to be initialized when + testing properties + * Added pulseaudio mock to renderer tests + * Added small code comment in renderer tests + * Solved problem with mute not enable in renderer tests + * Fixed problem when adding elements to a mock playlist in renderer + tests + * Fixed problem with playlist size initialization in playlist iterator in + renderer that was causing problems in the tests + * Fixed a bug in the tests when testing the stats updating + * Fixed problem with playlist reference count in renderer tests. + * Replaced pulsesink and xvimagesink by fakesink in renderer tests + + -- Juha Kellokoski Fri, 03 Jul 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.27-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.27-2 + * Fixes: NB#123701 - mafw-dbus-wrapper-76CE-6-1218.rcore.lzo crashed + * Fixes: NB#116836 - Streaming stops after taping on Next button during paused state + * Fixes: NB#123545 - mafw-gst-renderer get-position returns uninitialized value + * Fixes: NB#124469 - Device going to power saving mode after seek for video streams + * Fixes: NB#124116 - Position not progressing after seeking + * Fixes: NB#117860 - Inability to handle multiple resource of UPnP items + * Make sure we stay paused while buffering. + * Always keep worker->state up-to-date. + + -- Juha Kellokoski Tue, 30 Jun 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.27-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.27-1 + * Added function to remove the _set_timeout and used not to call it twice + innecessarily. + * Changed the order of adding the timeout to set the volume to pulse and + calling the timeout immediately as that runs on the mainloop and that + execution is delayed to that + * Checked for NULL operation when writing volume to pulse to avoid + crashes. Critical used instead. + * In _ext_stream_restore2_read_cb changed assertion for critical when + getting eol < 0 in volume manager + * Discarded ext_stream_restore2 volume event when eol < 0. Crash avoided + + -- Juha Kellokoski Thu, 25 Jun 2009 13:40:00 +0300 + +mafw-gst-renderer (0.1.2009.26-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.26-1 + * Check for NULL in the current_metadata. + * Removed useles warning message + * Updating states and blanking even if we do not need to report them. + * Move pause notification from _do_play to handle_buffering. + * Build fix. + * make _get_position be a state dependant function. + On Transitioning it sets position to 0, on Stopped raises an error. + * Improved state management in mafw-gst-renderer + when seeking (compare new state with worker state to decide + if we have to ignore the state transition and in any case, + always update the worker state). + * Activate playbin2 flags. Speeds up video start time to half. + + -- Juha Kellokoski Tue, 23 Jun 2009 14:33:24 +0300 + +mafw-gst-renderer (0.1.2009.25-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.25-2 + * Fixes: NB#117207 - Random MAFW-DBUS-WRAPPER crashes observed + * Fixes: NB#104213 - 'Unable to Find Media file' information note not displayed when MMC removed. + * Fixes: NB#116426 - Renderer fails to go to pause state when media clips are seeked while streaming + * Fixes: NB#121545 - pa_context_connect fails though flag PA_CONTEXT_NOFAIL set + * Fixes: NB#120942 - State changed signal is not coming when play issued right after seeking with video streams + * Extended current metadata function. + + -- Juha Kellokoski Mon, 15 Jun 2009 14:00:00 +0300 + +mafw-gst-renderer (0.1.2009.25-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.25-1 + * Reconnection to pulse is done using an idle call in volume manager. + * If PLAY is issued rigtht after seek, + signal the state change when we are done buffering. Still, + there is a problem because we should not execute a PLAY command + while buffering... + + -- Juha Kellokoski Fri, 12 Jun 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.24-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.24-3 + * Fixes: NB#118019 - Playing a clip of Unsupported format will stop the playback. + * Fixes: NB#120378 - Video doesn't start to play when clicked + * Stop setting volume if pulse is down in volume manager + * Written small workaround for problem when getting an empty error while + saving a pixbuf in renderer worker. + * Log a warning message when processing an error. + * A mafw-gst-renderer dependency version shortened to 0.9.15-1. + + -- Juha Kellokoski Wed, 10 Jun 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.24-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.24-2 + * Added "mafw_renderer_get_current_metadata" function to the API. + + -- Juha Kellokoski Mon, 08 Jun 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.24-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.24-1 + * Fixes: NB#120287 - Video recorded using other camera (e.g. Canon, Pentax) not playing smooth + * Fixed debug for seekability in renderer. + * We check always GStreamer seekability unless it is reported FALSE from + source. + + -- Juha Kellokoski Thu, 04 Jun 2009 10:00:00 +0300 + +mafw-gst-renderer (0.1.2009.23-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.23-2 + * Fixes: NB#119440 - Dbus wrapper crashes when high bit rate, high resolution clips are played. + * Fixes: NB#119613 - Random mafw-dbus-wrapper coredump generated after booting. + * Fixes: NB#119467 - Deleted playlists are not freed. + * Fixes: NB#118459 - Cannot play Nokia 5800 video clip + * Fixes: NB#115776 - Renderer state changed signal are not coming when playing and seeking video + * No need to ref the playlist in the renderer, + the playlist manager does that already. + * Changed to avoid setting the timeout if neither volume nor mute have + changed in volume manager + * Changed debug messages in volume manager + * We ignore mute if it not enabled (default = not enabled). + * Added option to configure.ac to disable or enable mute handling + * Use g_error_new_literal if string is constant. + + -- Juha Kellokoski Tue, 02 Jun 2009 12:00:00 +0300 + +mafw-gst-renderer (0.1.2009.23-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.23-1 + * Signalling volume when reconnecting to pulse + * Added code to reconnect to pulse when it gets disconnected. + * Closure callback for initialization is not called if it is NULL in + volume manager. + * Created _connect function to connect with pulse and reworker the init + function in volume manager. + * Created _destroy_context function and changed _destroy_idle function to + do it in volume manager. + + -- Juha Kellokoski Fri, 29 May 2009 10:30:00 +0300 + +mafw-gst-renderer (0.1.2009.22-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.22-3 + * Use appropriate error codes when the renderer cannot create + the playback pipeline and when there are problems with the + video window. + * Abort the renderer if cannot create pipeline ot sinks. + + -- Juha Kellokoski Wed, 27 May 2009 10:30:00 +0300 + +mafw-gst-renderer (0.1.2009.22-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.22-2 + * Fixes: NB#114972 - NP: AUdio- Taping on Next or Previous song volume goes to 0 level + * Fixes: NB#109166 - NP view - Volume bar does not popup for Hardware volume +/- key presses + * Fixes: NB#116070 - video plays before displaying unsupported resolution error message + * Fixes: NB#112697 - gst-renderer not listening volume change events from pulseaudio + * Fixes: NB#118001 - seekbar is disabled after playing a unsupported clip + * We switch volume manager volume to the just set so we signal it directly + when the change is requested. We don't signal any change in the volume + subscription if we have and operation in progress. + * We keep the pa_operation when writing the volume and cancel it and unref + it when a new one comes. + * Changed requested_* structure members to current_* in volume manager + * Renamed volume and mute structure members to pulse_* in volume manager. + * Fixed a small problem with mute. We were forgetting to set it when + sending it to pulse. + * Changed timeout interval for setting volume to 200ms. + * We just change the volume as soon as we get the first call and then we + add the timeout to filter following changes. + * Improved rounding volumes as we signalled different ones from the one we + were setting. + * Added protections to public methods so that they are not called when + volume manager is not alive yet. + * Minor changes when falling back to playbin usage. + + -- Juha Kellokoski Mon, 25 May 2009 12:21:24 +0300 + +mafw-gst-renderer (0.1.2009.22-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.22-1 + * If we fallback to use playbin instead of playbin2, check that + we can actually create an instance before attempting to + set properties. + * Changed to filter volume and mute changes to join several in a single + call. + * Setting property to ensure pulse role when initializing volume manager + in renderer. + * Separated role and its prefix for checking in volume manager in renderer + * Added more debug to volume manager in renderer + * Added dependencies to debian/control and configure.ac + * Migrated volume management in worker to use pulse implementation. + * Added code to manage volume with pulse. This code was developed in + collaboration with Marc-André Lureau in gitorious. Link: + http://gitorious.org/~calvaris/mafw-pulse/calvaris + * Added compilation of volume files to Makefile.am in renderer. + * Added empty files with licenses for volume management in renderer. + + -- Juha Kellokoski Fri, 22 May 2009 12:00:00 +0300 + +mafw-gst-renderer (0.1.2009.21-4) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.21-4 + * Reuse video sink component, do not create a new one every time + we play something. + * Ref audio and video sink before setting them, since we want them + to persist after the pipeline has been destroyed. + + -- Juha Kellokoski Wed, 20 May 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.21-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.21-3 + * Fixes: NB#104494 - Missing renderer error when video clib has high framerate + * Fixes: NB#115514 - UPnP:After seeking the seekbar on Pause state audio/video clip started playing + * Fixes: NB#115304 - Video playback will stop if we plug in usb cable + * Fixes: NB#115299 - Media player seek bar comes to starting position if usb cable is pluged in + * Properly initialize variable. + * Use specific error codes for unsupported + resolution and unsupported fps conditions. + + -- Juha Kellokoski Tue, 19 May 2009 10:30:00 +0300 + +mafw-gst-renderer (0.1.2009.21-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.21-2 + + -- Juha Kellokoski Mon, 18 May 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.21-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.21-1 + * Fixes: NB#117015 - video plays in windowed mode + * Fixes: NB#100842 - seeking in aac over http fails + * Fixes: NB#115126 - Media Player abuses Tracker API while playing songs + * Do not reuse video sink for now, somehow it gets + broken after some time and as a result we cannot set XID of target + video window to use. + * Create and configure audio and video sinks once, then provide these + to playbin2 when a new pipeline is created. + * Changed _handle_duration to use _check_duration and added for + seekability too. + * Reworked _query_duration_and_seekability into _check_duration and _check + seekability. + * Added timeout to query duration and seekability and changed when it was + called. + * Set timeout id to 0 when timeout is removed + * Used function to compare duration in seconds instead of comparing pure + nano seconds. + * Added function to compare durations in seconds. + * Query duration and seek after buffering is complete. + * Reworked adding the timeout to use a function. + * Modified seekability emission to be done only with changes and adapted + to type changes. + * Modified duration emission to be emitted only with changes. + * Media seekability initialized to unknown when playback begins. + * SeekabilityType renamed and moved to renderer worker. + * Renderer worker seekability uses its type now instead of gboolean. + * Stored duration and seek timeout id to be able to remove it later in + renderer worker. + * Added query for duration and seekability some seconds after going to + PLAYING to have more accurate information from GStreamer in renreder + worker. + * Reworked _finalize_startup in renderer worker into _finalize_startup and + _query_duration_and_seekability. The first uses now the second. + * Set pulse sink's latency-time property to half of buffer-time, + this provides double buffering capabilities and should also help + with avoiding audio glitches. Also, removed buffering for volume + pipeline, since that is not needed. + + -- Juha Kellokoski Thu, 14 May 2009 10:09:18 +0300 + +mafw-gst-renderer (0.1.2009.20-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.20-1 + * Fixes: NB#107221 - Connecting and disconnecting with an AP while playing an MP3 cause audio breakage + * Fixes: NB#114181 - Video aspect ratio is not preserved + * Set appropriate buffering settings for audio sink + to avoid audio glitches on certain scenarios. + * Maintainer changed in control file. + + -- Juha Kellokoski Thu, 07 May 2009 11:00:00 +0300 + +mafw-gst-renderer (0.1.2009.19-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.19-1 + * Fixes: NB#108833 - [Onsite] Audio volume is turned full when song is changed + * Fixes: NB#113464 - Dbus connection lost error message displayed in while trying to up/down volume level from MTG + * Fixes: NB#96483 - Gstreamer renderer does not change from GST_STATE_PAUSED to GST_STATE_READY after timeout + * Fixes: NB#104494 - Missing renderer error when video clib has high framerate + * Fixes: NB#110654 - mafw-dbus-wrapper prints about a critical error + * Fixes: NB#103987 - Audio playback is breaking for few seconds while playing mms live streams. + * Fixes power management issues caused by the volume pipeline + being always in PAUSED state. While a proper implementation + for volume management is not in place, this temporal fix + workarounds the problem by setting it to PAUSED on demand + when global volume has to be read, and then setting it back + to READY. + * Do not check video caps on prepare-xwindow-id, instead wait + for prerolling to finish, otherwise caps are not set yet. + * Use 'g_timeout_add_seconds' where possible. + * Added monitor volume hack again, but when going to playing instead of + stopping. + * Revert "Added monitor_hack again but called only when stopping the worker." + * Added monitor_hack again but called only when stopping the worker. + * Revert "Added dirty hack to be aware of the volume changes that don't + happen" + * Revert "Hack to monitor volume does not return TRUE, but FALSE and + reinstalls" + * Improved playlist-change handling + * Hack to monitor volume does not return TRUE, but FALSE and reinstalls + the timeout not to do it at regular intervals, but a timeout after last + callback is run. + * Do not go to PLAYING state until we are done buffering. + * Disable native flags in playbin2 temporarily since this is causing + trouble with some videos. Also, allow videos with resolution + up to 848x576. + * Added dirty hack to be aware of the volume changes that don't happen + inside mafw code. As soon as GStreamer adds notify::volume signals, we + have to remove this immediately. + * Setting volume when playing is done only for playback volume. + * Moved volume management from playback pipeline to volume pipeline. We + don't update the volume pipeline because it should be updated but we do it + with the playback one because it has always to be set up. + * Moved listening to volume signals from playback pipeline to volume + pipeline in renderer + * Added customized pipeline to always listen to volume changes in renderer + * Added support to distinguish between audio and video codec errors. + * Fixed critical warning message. + * When we get the ckey coming from the sync bus to the async bus, we check + the caps and raise an error if they are not suitable. + + -- Juha Kellokoski Thu, 30 Apr 2009 10:00:00 +0300 + +mafw-gst-renderer (0.1.2009.18-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.18-1 + + -- Juha Kellokoski Thu, 23 Apr 2009 15:30:00 +0300 + +mafw-gst-renderer (0.1.2009.17-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.17-2 + + -- Juha Kellokoski Fri, 17 Apr 2009 09:18:07 +0300 + +mafw-gst-renderer (0.1.2009.17-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.17-1 + + -- Mika Tapojärvi Wed, 15 Apr 2009 15:59:15 +0300 + +mafw-gst-renderer (0.1.2009.16-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.16-2 + * Fixes: NB#110043 - Mafw-dbus-wrapper crash is observed while switching between proxy playlist in a particular case + * Fixes: NB#108725 - DLNA CTT tool gives a failed verdict on "MT HTTP Header: Range - use in HEAD/GET requests" + * Added pre-unmount signal handling in the renderer. + * Added debug for seekability in renderer. + * Renderer uses now as first choice seekability coming from source and if not + defined, we query GStreamer as it happened so far. + * Added requesting seekability to source in renderer. + * Added support for seekability coming from source in renderer. + * Removed assumption of positive seekability for local files with known + duration. + * If GStreamer cannot answer to a request for seekability, we assume it is + not. + + -- Mika Tapojärvi Tue, 14 Apr 2009 15:06:34 +0300 + +mafw-gst-renderer (0.1.2009.16-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.16-1 + * All tags reported by Gst are referenced and freed later on, + when they have emitted to clients. However, _emit_renderer_art + was obtaining a reference to a tag value and freeing that + reference when done, leading to a double unref later on. + + -- Mika Tapojärvi Wed, 08 Apr 2009 12:41:14 +0300 + +mafw-gst-renderer (0.1.2009.15-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.15-2 + + -- Juha Kellokoski Fri, 03 Apr 2009 09:17:14 +0300 + +mafw-gst-renderer (0.1.2009.15-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.15-1 + * Fixes: NB#106136 - Metadata not shown properly for radio stations. + * Added transport-actions property. For the moment contains information + about Seek operation. + * Unit test disabled by default for system integration purposes. + * Some tags are detected when Gstreamer is already + in GST_STATE_PLAYING, so in this case, emit them right away, otherwise + they are never emitted to the UI. + + -- Juha Kellokoski Fri, 03 Apr 2009 09:17:14 +0300 + +mafw-gst-renderer (0.1.2009.14-4) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.14-4 + * Update playcount id should be 0 while _notify_play is run in renderer + so it doesn have sense checking about that + * Removed update_playcount_needed as behavior can be accoplished only with + timeout id in renderer. + * Moved the code to remove the update_playcount to the state class to fix + state pattern. + * _update_playcount_cb made public inside the renderer to be called from other + parts of it. + * Moved the code to add the timeout to state-transitioning. + * Moved the code from the state notify_eos in the base renderer to the + state-playing. + * Added initialization of the update_playcount structures in renderer. + + -- Juha Kellokoski Wed, 01 Apr 2009 09:34:16 +0300 + +mafw-gst-renderer (0.1.2009.14-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.14-3 + * Fixes: NB#107595 - Xid not set error comes when video is played to the end and then played again with media player + * Removed guessing the seekability from renderer in favor of only GStreamer + query. + * Setting pipeline to NULL without checking for async changes as it + cannot happen according to Stefan comments. + * Must always stop() on EOS when there are + no more items to play. This frees X resources if playing video, + otherwise setting a new Xid afterward leads to a BadWindow X + error. + * Enabling gstreamer optimization flags + * Creating pipeline at startup and, soon after the playback has ended, to + speed up the starting of the playback + + -- Juha Kellokoski Tue, 31 Mar 2009 09:20:00 +0200 + +mafw-gst-renderer (0.1.2009.14-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.14-2 + + -- Juha Kellokoski Mon, 30 Mar 2009 09:23:13 +0200 + +mafw-gst-renderer (0.1.2009.14-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.14-1 + + -- Juha Kellokoski Fri, 27 Mar 2009 09:30:00 +0200 + +mafw-gst-renderer (0.1.2009.13-5) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.13-5 + * Fixes: NB#102972 - Pause AAC clip from UPnP server timer shows as 00:00 + * Changed _notify_buffer_status in state-transitioning in renderer + to use the do_notify_buffer status as code was the same after removing timer + * Removed timer support from renderer utils + * Removed timer handling in renderer. + * Removed timer use from renderer get_position + * Changed API to return gint instead if guint in the get_position + callback + * Set Visa as integrator. + * Upgrade copyright year. + * Add headers for Makefile.am and configure.ac files. + * Set Visa Smolander as the contact person in headers. + + -- Juha Kellokoski Thu, 26 Mar 2009 09:53:00 +0200 + +mafw-gst-renderer (0.1.2009.13-4) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.13-4 + + -- Juha Kellokoski Wed, 25 Mar 2009 09:16:48 +0200 + +mafw-gst-renderer (0.1.2009.13-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.13-3 + + -- Juha Kellokoski Tue, 24 Mar 2009 09:23:08 +0200 + +mafw-gst-renderer (0.1.2009.12-4) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.12-4 + + -- Juha Kellokoski Wed, 18 Mar 2009 09:17:01 +0200 + +mafw-gst-renderer (0.1.2009.12-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.12-3 + * Fixed CID 610 + * Fixed CID 2592 + + -- Juha Kellokoski Wed, 18 Mar 2009 09:17:01 +0200 + +mafw-gst-renderer (0.1.2009.12-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.12-2 + * Fixes: NB#102172 - Total clip duration shown wrongly for vbr clips. + * Fixes: NB#105468 - Mafw-dbus-wrapper freezes when commands are given consecutively + * Corrected the double tag emission when pausing in transitioning + and going to GST_STATE_READY in renderer worker + * Moved _free_taglist functions above in renderer worker + * Removed tag_list as global variable to be inside the renderer + worker + * Modified other functions according this point + * Moved _add_ready_timeout from _construct_pipeline to _do_play + in renderer to allow us going to ready just after building the pipeline + because in the other case we hadn't received the seekability yet. + * Solved a memory leak when freeing the tag list in renderer worker + * Changed going to GST_STATE_READY only for seekable streams in renderer + worker + * Code to go to GST_STATE_READY after sometime in PAUSED in renderer + worker reactivated + * Added checks for NULL buffers when emitting the current frame on + paused. + + -- Juha Kellokoski Tue, 17 Mar 2009 09:21:54 +0200 + +mafw-gst-renderer (0.1.2009.11-6) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-6 + + -- Juha Kellokoski Thu, 12 Mar 2009 09:13:36 +0200 + +mafw-gst-renderer (0.1.2009.11-5) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-5 + + -- Juha Kellokoski Thu, 12 Mar 2009 09:13:36 +0200 + +mafw-gst-renderer (0.1.2009.11-4) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-4 + + -- Juha Kellokoski Wed, 11 Mar 2009 09:09:57 +0200 + +mafw-gst-renderer (0.1.2009.11-3) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-3 + + -- Juha Kellokoski Tue, 10 Mar 2009 09:12:41 +0200 + +mafw-gst-renderer (0.1.2009.11-2) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-2 + * Fixes: NB#104680 - System UI freezed while playing high resolution albumart clips + * Added buffering info test in renderer + * Added support to get buffering information in renderer tests + * Added tests for properties management in renderer tests + * Solved a problem that could cause some race conditions in volume handling + in renderer worker + * Moved _set_volume and _set_mute functions in the worker file in + renderer. + * Added support to manage properties values in renderer tests + * Added test for duration emission in renderer tests + * Added test for get_position in renderer + * Added tests for media art emission in renderer + * Added GStreamer tag management in renderer tests + * Added testframe.png to renderer tests + * Activated and fixed video tests compilation in renderer + * Added functions to metadata checks in renderer tests. + * Added error policy tests in renderer + * Added support in renderer tests to receive expected error callbacks + * Fixed problem with update lastplayed in renderer tests. + * Solved problem in playcount renderer tests. + + -- Juha Kellokoski Mon, 09 Mar 2009 09:24:38 +0200 + +mafw-gst-renderer (0.1.2009.11-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.11-1 + * Notify when third-party applications modify gstreamer volume. + * Enabling unit tests to mafw-gst-renderer. + * Update playcount and last-played when reaching EOS, or after 10 seconds. + + -- Juha Kellokoski Thu, 05 Mar 2009 14:43:54 +0200 + +mafw-gst-renderer (0.1.2009.10-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.10-1 + * Fixed segfault + * Removed gstreamer0.10-selector from the mafw-gst-renderer dependencies. + * Allow blanking when TV Out cable is plugged. + + -- Juha Kellokoski Thu, 26 Feb 2009 14:06:24 +0200 + +mafw-gst-renderer (0.1.2009.9-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.09-1 + * Disabling optimization flags + * Removing helixbin usage + * Delaying metadatas received from gstreamer + * Preload some gst plugins at startup + * Adding some optimization flags to the pipeline + + -- Juha Kellokoski Thu, 19 Feb 2009 17:26:04 +0200 + +mafw-gst-renderer (0.1.2009.8-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.08-1 + * Fixes: NB#98725 + * Fixes: NB#100158 + * Added Nokia copyright to gstcreenshot.[ch] files in renderer. + * Made bvw_frame_conv_convert asynchronous + * Adapted renderer worker to that conversion. + * Reusing pipeline for color space conversion in renderer. + * Changed raw video art saving to use bacon video widget conversion in + mafw-gst-renderer + * Added gstsnapshot.[ch] files to renderer to convert raw frames into + understandable format for gdk_pixbuf + * Changed tmp file name for gstreamer renderer emitted art + * Changed function _save_graphic_file_from_gst_buffer to + save an unconverted video/x-raw-rgb. + * Revert "Fake function that copies a fake frame to test" + + -- Juha Kellokoski Thu, 12 Feb 2009 14:22:39 +0200 + +mafw-gst-renderer (0.1.2009.07-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.07-1 + * Changed highest resolution for fremantle + + -- Mika Tapojärvi Fri, 06 Feb 2009 08:38:37 +0200 + +mafw-gst-renderer (0.1.2009.06-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.06-1 + * Changed highest resolution for fremantle + * Added GTK_DISABLE_DEPRECATED parameters for mafw-tracker-source + configure.ac. Added extended descriptions for + * mafw-gst-renderer. + * Build fix + + -- Mika Tapojärvi Fri, 30 Jan 2009 14:03:15 +0200 + +mafw-gst-renderer (0.1.2009.05-1) unstable; urgency=low + + * MAFW gst renderer, pre-release 0.2009.05-1 + * Changing the base class of the extension objects to GInitiallyUnowned + * Changed testframe.jpeg + + -- Mika Tapojärvi Thu, 22 Jan 2009 14:33:27 +0200 + +mafw-gst-renderer (0.1.2009.04-1) unstable; urgency=low + + * MAFW, pre-release 0.2009.04-1 + * Changed testframe.jpeg + * Reducing lintian warnings. + * Fixes: NB#97304 + * Fixes: NB#94990 + * Fixes: NB#85894 + + -- Mika Tapojärvi Fri, 16 Jan 2009 14:59:38 +0200 + +mafw-gst-renderer (0.1.2009.03-1) unstable; urgency=low + + * MAFW, pre-release 0.1.2009.03-1 + * Testing the error reporting when resuming in transitioning state + without having paused. + * Sent a GError if trying to resume in transitioning state without + having paused before. + * Reset timer when stopping. + * Remated stream_timer_* functions into media_timer_* because + name was confusing. + * Renamed _is_playlist into uri_is_playlist and moved to -utils in + renderer. + * Changed its calls to use the new name. + * Changed condition about reporting the error when performing _do_seek. + * Added comment about the playlist mode error handling in renderer + worker. + * Changed tagmap array for a GHashTable in renderer worker. + * Removed playback mode and pl struct from global variables and added + to worker structure. + * Added worker as parameter to _reset_pl_info and _on_pl_entry_parsed + in renderer because it is needed to use the parameters inside worker structure. + * Removed old_error handler because it was not being used. + * In renderer worker renamed: + * NORMAL_MODE -> WORKER_MODE_SINGLE_PLAY + * PLAYLIST_MODE -> WORKER_MODE_PLAYLIST + * Changed _metadata_set_cb to g_debug if operation was correct or if + it failed. + * Reindented _update_playcount_metadata_cb parameters in renderer. + * Logged error in _update_playcount_metadata_cb in renderer because + it was just being ignored. This causes the tests to log the warning. + * Added a meaningful error to a test in renderer. + * Fixed _update_playcount_metadata_cb documentation in renderer. + * Renamed _playcount_metadata into _update_playcount_metadata_cb in + renderer. + * mafw_gstreamer_renderer_get_position changed not to test for + callback != NULL because it is already being checked in preconditions. + * Changed mafw_gstreamer_renderer_get_status not to test callback != NULL + because it is already checked in preconditions. + * In mafw_gstreamer_renderer_get_status added check for callback != NULL + in preconditions. + * Renamed unable_play_count into play_failed_count in renderer. + * Added G_LOG_DOMAIN for check-mafw-gstreamer-renderer. + * Removed the CHECK-MLS traces from renderer tests. + * Changed the G_LOG_DOMAIN for given files to have more suitable ones + in renderer. + + -- Mika Tapojärvi Thu, 08 Jan 2009 16:13:10 +0200 + +mafw-gst-renderer (0.1.2008.52-1) unstable; urgency=low + + * Renamed midas to mafw + + -- Zeeshan Ali Mon, 22 Dec 2008 12:55:59 +0200 + +midas-gstreamer-renderer (0.1.2008.52) unstable; urgency=low + + * Removing libtotem-pl-parser + * Deactivated code of the timeout to go to ready until bug with gstreamer is clarified. + * Added functions to add, remove and timeout function itself to switch from PAUSED to READY in renderer worker. + Used this functions to implement the behavior when pausing. + * Fixes NB#85894 incorrect duration reported + * Fixes NB#93484 Playlist format other than wpl and ram shows its icon incorrectly in Playlists container + * Fixes NB#94990 Gstreamer renderer gives seekable metadata TRUE for radio stream + + + -- Zeeshan Ali Fri, 19 Dec 2008 15:28:58 +0200 + +midas-gstreamer-renderer (0.1.2008.51-1) unstable; urgency=low + + * (ha)xmas: hardwire ximagesink is removed + + -- Zeeshan Ali Mon, 15 Dec 2008 12:55:59 +0200 + +midas-gstreamer-renderer (0.1.2008.51) unstable; urgency=low + + * (ha)xmas: hardwire ximagesink temporarily + + -- Zeeshan Ali Fri, 12 Dec 2008 14:57:37 +0200 + +midas-gstreamer-renderer (0.1.2008.50) unstable; urgency=low + + * Added get_last_index method the the mock playlist in renderer + tests. + * Added new test cases for various use cases. + + -- Zeeshan Ali Fri, 05 Dec 2008 13:29:06 +0200 + +midas-gstreamer-renderer (0.1.2008.49) unstable; urgency=low + + * In development. + + -- Zeeshan Ali Fri, 28 Nov 2008 14:35:35 +0200 + +midas-gstreamer-renderer (0.1.2008.48) unstable; urgency=low + + * In renderer: + --Renamed _get_graphic_file_path into _get_tmp_file_from_pool and + changed to use the tmp files pool. + --Replaced the calls to use _get_tmp_file_from_pool with the + proper changes to parameters and return values. + * Added resume operation to transitioning state in renderer that + allows to resume in transitioning. + * Added stop after pausing while transitioning to finish process in a better + way in renderer tests. + * Implemented HAL listener which stops renderer when usb cable is plugged + in, and we are playing something from memory card + * Changed do_next and do_prev to begin playback if pressing next + or prev when going beyond the playlist limits in renderer. + * Added mechanism to start/stop wrappers on package install/removal + (ignoring scratchbox support for now). Uses DSME. + Added an Xsession.post script. + Updated affected components + * Fixes: NB#92843 MidasRenderer ""playlist-changed"" and ""media-changed"" signals occur in random order when changing playlist + * Fixes: NB#92238 Play state not preserved when next pressed in the end of the playlist. + + -- Zeeshan Ali Fri, 21 Nov 2008 16:59:53 +0200 + +midas-gstreamer-renderer (0.1.2008.47) unstable; urgency=low + + * Send error signal when handling playback errors. + * Added conic network detection in renderer + * Added error handing in renderer for CODEC_NOT_FOUND , seek error, network errors, DRM errors. + * Fixes: NB#87297 Property of shuffle operation not reflected to the last item of playlist + * Fixes: NB#87354 shuffle not applied to dynamically added clips + * Fixes: NB#87667 Midas-playlist-daemon crashes with repeat mode 'on' & playing unsupported clip + * Fixes: NB#91530 Media-changed signal comes everytime item is added to playlist + * Fixes: NB#91566 glib criticals observed when audio playback is stopped + * Fixes: NB#87841 Next and previous gives ""Index out of bounds error"" on last and first items + * Fixes: NB#91893 local sink crashes when invalid playlist items are played + + -- Zeeshan Ali Fri, 14 Nov 2008 12:30:41 +0200 + +midas-gstreamer-renderer (0.1.2008.46) unstable; urgency=low + + * Fixes: NB#91061 gstreamer-gnomevfs pkgs missing. + + -- Zeeshan Ali Fri, 07 Nov 2008 11:58:32 +0200 + +midas-gstreamer-renderer (0.1.2008.45) unstable; urgency=low + + * Implemented move_to_last based on playlist get_last_index function + * Implemented tag emission of renderer art in MidasGstreamerRenderer. + + -- Zeeshan Ali Fri, 31 Oct 2008 14:14:16 +0200 + +midas-gstreamer-renderer (0.1.2008.44) unstable; urgency=low + + * Added support to request the current frame + * Added support for the property in the renderer and its storage on the worker. + * added gdkpixbuf dependency. + + -- Zeeshan Ali Fri, 24 Oct 2008 10:11:52 +0300 + +midas-gstreamer-renderer (0.1.2008.43) unstable; urgency=low + + * Renaming local-sink->midas-gstreamer-renderer + * support of playback on diablo + + -- Zeeshan Ali Fri, 17 Oct 2008 15:03:14 +0300 + +midas-gstreamer-renderer (0.1.2008.42) unstable; urgency=low + + * Fixes: NB#89265 unable to parse wpl playlist format files in local sink + + -- Zeeshan Ali Fri, 10 Oct 2008 16:10:02 +0300 + +midas-gstreamer-renderer (0.1.2008.40) unstable; urgency=low + + * Using playbin2. + + -- Zeeshan Ali Mon, 29 Sep 2008 07:02:28 +0000 + +midas-gstreamer-renderer (0.1.2008.39) unstable; urgency=low + + * Fixes: NB#87723 Play item pointer switches back to first item of playlist while repeat mode of playlist is not enabled + + -- Zeeshan Ali Sun, 21 Sep 2008 18:35:06 +0300 + +midas-gstreamer-renderer (0.1.2008.38) unstable; urgency=low + + * Unit tests fixed after the use_count API addition. + + -- Zeeshan Ali Mon, 15 Sep 2008 08:11:12 +0300 + +midas-gstreamer-renderer (0.1.2008.37) unstable; urgency=low + + * small face-lift (configure.ac, Makefile.am:s and build fixes) + * Fixes: NB#86160 Media continues to play after deleting the playlist + + -- Zeeshan Ali Mon, 08 Sep 2008 08:37:04 +0300 + +midas-gstreamer-renderer (0.1.2008.36) unstable; urgency=low + + * Fixes: NB#87757 Seeking gives unknown seek mode as error in callback function + * Fixes: NB#87414 Seek option is not enabled for mp3 format files + * Fixes: NB#87463 playback is switched back to 20 seconds time stamp when the forward button is pressed + * Fixes: NB#87524 Video full screen turns blank during pause playback state + + -- Zeeshan Ali Mon, 01 Sep 2008 08:21:43 +0300 + +midas-gstreamer-renderer (0.1.2008.35) unstable; urgency=low + + * In development. + + -- Zeeshan Ali Sun, 24 Aug 2008 19:42:40 +0300 + +midas-gstreamer-renderer (0.1.2008.34) unstable; urgency=low + + * More strict parameter checking by set_position(). + + -- Zeeshan Ali Fri, 15 Aug 2008 09:48:16 +0300 + +midas-gstreamer-renderer (0.1.2008.33) unstable; urgency=low + + * Initial release. + * Fixes: NB#85491 stop() in transitioning state + * Fixes: NB#85894 incorrect duration reported + * Fixes: NB#85481 Unable to seek attached mp3 file using MAFW api + * Fixes: NB#85675 sink::metadata-changed signal reports the is-seekable key in integer + * Fixes: NB#86692 local-sink doesn't emit buffering-info signals + * Fixes: NB#85161 attempts to play media having unsupported format results in error message + * Fixes: NB#85160 GLIB CRITICAL message trying to get the iradio-name metadata from gstreamer + * Fixes: NB#85892 Pausing resets playback + * Fixes: NB#85897 media always reported to be unseekable + * Fixes: NB#86160 Media continues to play after deleting the playlist + * Fixes: NB#86654 crash while playing from a playlist + * Fixes: NB#85149 play(callback) is not invoked + * Fixes: NB#85150 only the first item of the playlist is played + * Fixes: NB#85498 sink should advance to next item if current is unplayable + * Fixes: NB#86893 UPnP media content not playing + * Fixes: NB#85472 play() starts last play_object()ed item again + * Fixes: NB#85475 assign_playlist() starts playing + * Fixes: NB#85479 misleading index in media-changed for play_object() + * Fixes: NB#85628 crash if invoked without callback + * Fixes: NB#87082 gstreamer criticals during playback + * Fixes: NB#85489 critical warnings via assign_playlist(NULL) + * Fixes: NB#87084 assign_playlist() critical warnings + * Fixes: NB#86956 midas-dbus-wrapper dies when playback is attempted in mentioned scenario + + -- Zeeshan Ali Sun, 10 Aug 2008 19:52:39 +0300 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f475874 --- /dev/null +++ b/debian/control @@ -0,0 +1,28 @@ +Source: mafw-gst-renderer +Section: misc +Priority: optional +Maintainer: Juha Kellokoski +Build-Depends: debhelper (>= 4.0.0), libglib2.0-dev, + libgstreamer0.10-dev (>= 0.10.20-0maemo3), + libgstreamer-plugins-base0.10-dev (>= 0.10.20-0maemo5), + libx11-dev, libosso-dev (>= 2.0), libmafw0-dev (>= 0.1), + checkmore, gstreamer0.10-plugins-base, + gstreamer0.10-plugins-good, + libhal-dev, libtotem-plparser-dev, libpulse-dev (>= 0.9.15-1), + libgconf2-dev, libosso-gnomevfs2-dev +Standards-Version: 3.7.2 + +Package: mafw-gst-renderer +Section: libs +Architecture: any +Depends: gconf2, ${shlibs:Depends}, ${misc:Depends} +Description: MAFW gst renderer plugin + Renderer plugin for MAFW-gst + +Package: mafw-gst-renderer-dbg +Section: devel +Architecture: any +Priority: extra +Depends: mafw-gst-renderer (= ${binary:Version}) +Description: debug symbols for mafw-gst-renderer + MAFW-gst renderer debug symbols diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..79772e7 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,6 @@ +Copyright (C) 2007, 2008, 2009 Nokia Corporation. All rights reserved. + +Contact: Visa Smolander + + + diff --git a/debian/mafw-gst-renderer.install.in b/debian/mafw-gst-renderer.install.in new file mode 100644 index 0000000..f350be5 --- /dev/null +++ b/debian/mafw-gst-renderer.install.in @@ -0,0 +1 @@ +@plugindir@/*.so diff --git a/debian/mafw-gst-renderer.postinst b/debian/mafw-gst-renderer.postinst new file mode 100644 index 0000000..924970d --- /dev/null +++ b/debian/mafw-gst-renderer.postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +#DEBHELPER# + +test -x /usr/bin/mafw.sh && /usr/bin/mafw.sh start mafw-gst-renderer -7 \ +|| true; diff --git a/debian/mafw-gst-renderer.prerm b/debian/mafw-gst-renderer.prerm new file mode 100644 index 0000000..084ef87 --- /dev/null +++ b/debian/mafw-gst-renderer.prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +#DEBHELPER# + +test -x /usr/bin/mafw.sh && /usr/bin/mafw.sh stop mafw-gst-renderer \ +|| true; diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..1e89b65 --- /dev/null +++ b/debian/rules @@ -0,0 +1,96 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +ifneq (,$(findstring thumb,$(DEB_BUILD_OPTIONS))) + CFLAGS += -mthumb +endif + +ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 -ggdb3 -finstrument-functions +endif + +ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) + INSTALL_PROGRAM += -s +endif +maybe_coverage := $(if $(filter lcov,$(DEB_BUILD_OPTIONS)),--enable-coverage,) + +configure-stamp: + dh_testdir + CFLAGS="$(CFLAGS)" ./autogen.sh \ + --host=$(DEB_HOST_GNU_TYPE) \ + --build=$(DEB_BUILD_GNU_TYPE) \ + --disable-dependency-tracking \ + --prefix=/usr \ + $(maybe_coverage) + touch configure-stamp + +build: build-stamp +build-stamp: configure-stamp + dh_testdir + $(MAKE) +ifeq ($(findstring nocheck,$(DEB_BUILD_OPTIONS)),) + $(MAKE) check +endif + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + [ ! -f Makefile ] || $(MAKE) distclean + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + $(MAKE) install DESTDIR=$(CURDIR)/debian/tmp + +binary-indep: build install + +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_install --sourcedir=debian/tmp -v + dh_link + dh_strip --dbg-package=mafw-gst-renderer + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch + +distcheck: build + dh_testdir + $(MAKE) distcheck + +vg: + dh_testdir + $(MAKE) -C "$(CURDIR)/tests" vg + + +.PHONY: build clean binary-indep binary-arch binary install distcheck vg + diff --git a/libmafw-gst-renderer/Makefile.am b/libmafw-gst-renderer/Makefile.am new file mode 100644 index 0000000..a564ab9 --- /dev/null +++ b/libmafw-gst-renderer/Makefile.am @@ -0,0 +1,55 @@ +# +# Makefile.am for MAFW gst renderer library. +# +# Author: Zeeshan Ali +# +# Copyright (C) 2007, 2008, 2009 Nokia. All rights reserved. + +plugin_LTLIBRARIES = mafw-gst-renderer.la + +BUILT_SOURCES = mafw-gst-renderer-marshal.c \ + mafw-gst-renderer-marshal.h + +mafw_gst_renderer_la_SOURCES = $(BUILT_SOURCES) \ + blanking.c blanking.h \ + mafw-gst-renderer.c mafw-gst-renderer.h \ + mafw-gst-renderer-utils.c mafw-gst-renderer-utils.h \ + mafw-gst-renderer-worker.c mafw-gst-renderer-worker.h \ + mafw-gst-renderer-worker-volume.c mafw-gst-renderer-worker-volume.h \ + mafw-gst-renderer-state.c mafw-gst-renderer-state.h \ + mafw-gst-renderer-state-playing.c mafw-gst-renderer-state-playing.h \ + mafw-gst-renderer-state-paused.c mafw-gst-renderer-state-paused.h \ + mafw-gst-renderer-state-stopped.c mafw-gst-renderer-state-stopped.h \ + mafw-gst-renderer-state-transitioning.c mafw-gst-renderer-state-transitioning.h \ + mafw-playlist-iterator.c mafw-playlist-iterator.h + +mafw_gst_renderer_la_CPPFLAGS = $(DEPS_CFLAGS) $(VOLUME_CFLAGS) \ + -DPREFIX=\"$(prefix)\" $(_CFLAGS) +mafw_gst_renderer_la_LDFLAGS = -avoid-version -module $(_LDFLAGS) +mafw_gst_renderer_la_LIBADD = $(DEPS_LIBS) $(VOLUME_LIBS) \ + -lgstinterfaces-0.10 -lgstpbutils-0.10 + +if HAVE_GDKPIXBUF +mafw_gst_renderer_la_SOURCES += gstscreenshot.c gstscreenshot.h +mafw_gst_renderer_la_CPPFLAGS += $(GDKPIXBUF_CFLAGS) +mafw_gst_renderer_la_LIBADD += $(GDKPIXBUF_LIBS) +endif + +if HAVE_CONIC +mafw_gst_renderer_la_CPPFLAGS += $(CONIC_CFLAGS) +mafw_gst_renderer_la_LIBADD += $(CONIC_LIBS) +endif + +mafw-gst-renderer-marshal.c: mafw-gst-renderer-marshal.list + ( \ + echo '#include "mafw-gst-renderer-marshal.h"'; \ + $(GLIB_GENMARSHAL) --prefix=mafw_gst_renderer_marshal --body $^ \ + ) > $@ + +mafw-gst-renderer-marshal.h: mafw-gst-renderer-marshal.list + $(GLIB_GENMARSHAL) --prefix=mafw_gst_renderer_marshal --header \ + $^ > $@ + +EXTRA_DIST = mafw-gst-renderer-marshal.list +CLEANFILES = *.gcno *.gcda +MAINTAINERCLEANFILES = Makefile.in *.loT diff --git a/libmafw-gst-renderer/blanking.c b/libmafw-gst-renderer/blanking.c new file mode 100644 index 0000000..4a12836 --- /dev/null +++ b/libmafw-gst-renderer/blanking.c @@ -0,0 +1,119 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "blanking.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-blanking" + +/* In seconds */ +#define VIDEO_BLANKING_TIMER_INTERVAL 45 + +static guint blanking_timeout_id = 0; +static osso_context_t *osso_ctx = NULL; +static gboolean can_control_blanking = TRUE; +static gboolean is_blanking_prohibited = FALSE; + +static void remove_blanking_timeout(void) +{ + if (blanking_timeout_id) { + g_source_remove(blanking_timeout_id); + blanking_timeout_id = 0; + } +} + +/* + * Re-enables screen blanking. + */ +void blanking_allow(void) +{ + is_blanking_prohibited = FALSE; + remove_blanking_timeout(); +} + +static gboolean no_blanking_timeout(void) +{ + /* Stop trying if it fails. */ + return osso_display_blanking_pause(osso_ctx) == OSSO_OK; +} + +/* + * Adds a timeout to periodically disable screen blanking. + */ +void blanking_prohibit(void) +{ + is_blanking_prohibited = TRUE; + if ((!osso_ctx) || (!can_control_blanking)) + return; + osso_display_state_on(osso_ctx); + osso_display_blanking_pause(osso_ctx); + if (blanking_timeout_id == 0) { + blanking_timeout_id = + g_timeout_add_seconds(VIDEO_BLANKING_TIMER_INTERVAL, + (gpointer)no_blanking_timeout, + NULL); + } +} + +void blanking_init(void) +{ + /* It's enough to initialize it once for a process. */ + if (osso_ctx) + return; + osso_ctx = osso_initialize(PACKAGE, VERSION, 0, NULL); + if (!osso_ctx) + g_warning("osso_initialize failed, screen may go black"); + is_blanking_prohibited = FALSE; + /* Default policy is to allow user to control blanking */ + blanking_control(TRUE); +} + +void blanking_deinit(void) +{ + if (!osso_ctx) + return; + blanking_control(FALSE); + osso_deinitialize(osso_ctx); + osso_ctx = NULL; +} + +void blanking_control(gboolean activate) +{ + can_control_blanking = activate; + if (!can_control_blanking) { + remove_blanking_timeout(); + } else { + /* Restore the last state */ + if (is_blanking_prohibited) { + blanking_prohibit(); + } else { + blanking_allow(); + } + } +} diff --git a/libmafw-gst-renderer/blanking.h b/libmafw-gst-renderer/blanking.h new file mode 100644 index 0000000..6a69e61 --- /dev/null +++ b/libmafw-gst-renderer/blanking.h @@ -0,0 +1,37 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef BLANKING_H +#define BLANKING_H + +G_BEGIN_DECLS + +void blanking_init(void); +void blanking_deinit(void); +void blanking_allow(void); +void blanking_prohibit(void); +void blanking_control(gboolean activate); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/gstscreenshot.c b/libmafw-gst-renderer/gstscreenshot.c new file mode 100644 index 0000000..f7f177e --- /dev/null +++ b/libmafw-gst-renderer/gstscreenshot.c @@ -0,0 +1,259 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje + * Portion Copyright © 2009 Nokia Corporation and/or its + * subsidiary(-ies).* All rights reserved. * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstscreenshot.h" + +typedef struct { + GstBuffer *result; + GstElement *src; + GstElement *sink; + GstElement *pipeline; + BvwFrameConvCb cb; + gpointer cb_data; +} GstScreenshotData; + +/* GST_DEBUG_CATEGORY_EXTERN (_totem_gst_debug_cat); */ +/* #define GST_CAT_DEFAULT _totem_gst_debug_cat */ + +static void feed_fakesrc(GstElement *src, GstBuffer *buf, GstPad *pad, + gpointer data) +{ + GstBuffer *in_buf = GST_BUFFER(data); + + g_assert(GST_BUFFER_SIZE(buf) >= GST_BUFFER_SIZE(in_buf)); + g_assert(!GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_READONLY)); + + gst_buffer_set_caps(buf, GST_BUFFER_CAPS(in_buf)); + + memcpy(GST_BUFFER_DATA(buf), GST_BUFFER_DATA(in_buf), + GST_BUFFER_SIZE(in_buf)); + + GST_BUFFER_SIZE(buf) = GST_BUFFER_SIZE(in_buf); + + GST_DEBUG("feeding buffer %p, size %u, caps %" GST_PTR_FORMAT, + buf, GST_BUFFER_SIZE(buf), GST_BUFFER_CAPS(buf)); + + gst_buffer_unref(in_buf); +} + +static void save_result(GstElement *sink, GstBuffer *buf, GstPad *pad, + gpointer data) +{ + GstScreenshotData *gsd = data; + + gsd->result = gst_buffer_ref(buf); + + GST_DEBUG("received converted buffer %p with caps %" GST_PTR_FORMAT, + gsd->result, GST_BUFFER_CAPS(gsd->result)); +} + +static gboolean create_element(const gchar *factory_name, GstElement **element, + GError **err) +{ + *element = gst_element_factory_make(factory_name, NULL); + if (*element) + return TRUE; + + if (err && *err == NULL) { + *err = g_error_new( + GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "cannot create element '%s' - please check your " + "GStreamer installation", factory_name); + } + + return FALSE; +} + +static gboolean finalize_process(GstScreenshotData *gsd) +{ + g_signal_handlers_disconnect_matched(gsd->sink, (GSignalMatchType) + G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + save_result, NULL); + g_signal_handlers_disconnect_matched(gsd->src, (GSignalMatchType) + G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + feed_fakesrc, NULL); + gst_element_set_state(gsd->pipeline, GST_STATE_NULL); + + g_free(gsd); + + return FALSE; +} + +static gboolean async_bus_handler(GstBus *bus, GstMessage *msg, + gpointer data) +{ + GstScreenshotData *gsd = data; + gboolean keep_watch = TRUE; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + if (gsd->result != NULL) { + GST_DEBUG("conversion successful: result = %p", + gsd->result); + } else { + GST_WARNING("EOS but no result frame?!"); + } + gsd->cb(gsd->result, gsd->cb_data); + keep_watch = finalize_process(gsd); + break; + } + case GST_MESSAGE_ERROR: { + gchar *dbg = NULL; + GError *error = NULL; + + gst_message_parse_error(msg, &error, &dbg); + if (error != NULL) { + g_warning("Could not take screenshot: %s", + error->message); + GST_DEBUG("%s [debug: %s]", error->message, + GST_STR_NULL(dbg)); + g_error_free(error); + } else { + g_warning("Could not take screenshot(and " + "NULL error!)"); + } + g_free(dbg); + gsd->result = NULL; + gsd->cb(gsd->result, gsd->cb_data); + keep_watch = finalize_process(gsd); + break; + } + default: + break; + } + + return keep_watch; +} + +/* takes ownership of the input buffer */ +gboolean bvw_frame_conv_convert(GstBuffer *buf, GstCaps *to_caps, + BvwFrameConvCb cb, gpointer cb_data) +{ + static GstElement *src = NULL, *sink = NULL, *pipeline = NULL, + *filter1 = NULL, *filter2 = NULL; + static GstBus *bus; + GError *error = NULL; + GstCaps *to_caps_no_par; + GstScreenshotData *gsd; + + g_return_val_if_fail(GST_BUFFER_CAPS(buf) != NULL, FALSE); + g_return_val_if_fail(cb != NULL, FALSE); + + if (pipeline == NULL) { + GstElement *csp, *vscale; + + pipeline = gst_pipeline_new("screenshot-pipeline"); + if(pipeline == NULL) { + g_warning("Could not take screenshot: " + "no pipeline (unknown error)"); + return FALSE; + } + + /* videoscale is here to correct for the + * pixel-aspect-ratio for us */ + GST_DEBUG("creating elements"); + if(!create_element("fakesrc", &src, &error) || + !create_element("ffmpegcolorspace", &csp, &error) || + !create_element("videoscale", &vscale, &error) || + !create_element("capsfilter", &filter1, &error) || + !create_element("capsfilter", &filter2, &error) || + !create_element("fakesink", &sink, &error)) { + g_warning("Could not take screenshot: %s", + error->message); + g_error_free(error); + return FALSE; + } + + GST_DEBUG("adding elements"); + gst_bin_add_many(GST_BIN(pipeline), src, csp, filter1, vscale, + filter2, sink, NULL); + + g_object_set(sink, "preroll-queue-len", 1, + "signal-handoffs", TRUE, NULL); + + /* set to 'fixed' sizetype */ + g_object_set(src, "sizetype", 2, "num-buffers", 1, + "signal-handoffs", TRUE, NULL); + + /* FIXME: linking is still way too expensive, profile + * this properly */ + GST_DEBUG("linking src->csp"); + if(!gst_element_link_pads(src, "src", csp, "sink")) + return FALSE; + + GST_DEBUG("linking csp->filter1"); + if(!gst_element_link_pads(csp, "src", filter1, "sink")) + return FALSE; + + GST_DEBUG("linking filter1->vscale"); + if(!gst_element_link_pads(filter1, "src", vscale, "sink")) + return FALSE; + + GST_DEBUG("linking vscale->capsfilter"); + if(!gst_element_link_pads(vscale, "src", filter2, "sink")) + return FALSE; + + GST_DEBUG("linking capsfilter->sink"); + if(!gst_element_link_pads(filter2, "src", sink, "sink")) + return FALSE; + + bus = gst_element_get_bus(pipeline); + } + + /* adding this superfluous capsfilter makes linking cheaper */ + to_caps_no_par = gst_caps_copy(to_caps); + gst_structure_remove_field(gst_caps_get_structure(to_caps_no_par, 0), + "pixel-aspect-ratio"); + g_object_set(filter1, "caps", to_caps_no_par, NULL); + gst_caps_unref(to_caps_no_par); + + g_object_set(filter2, "caps", to_caps, NULL); + gst_caps_unref(to_caps); + + gsd = g_new0(GstScreenshotData, 1); + + gsd->src = src; + gsd->sink = sink; + gsd->pipeline = pipeline; + gsd->cb = cb; + gsd->cb_data = cb_data; + + g_signal_connect(sink, "handoff", G_CALLBACK(save_result), gsd); + + g_signal_connect(src, "handoff", G_CALLBACK(feed_fakesrc), buf); + + gst_bus_add_watch(bus, async_bus_handler, gsd); + + /* set to 'fixed' sizetype */ + g_object_set(src, "sizemax", GST_BUFFER_SIZE(buf), NULL); + + GST_DEBUG("running conversion pipeline"); + gst_element_set_state(pipeline, GST_STATE_PLAYING); + + return TRUE; +} diff --git a/libmafw-gst-renderer/gstscreenshot.h b/libmafw-gst-renderer/gstscreenshot.h new file mode 100644 index 0000000..d3cf23c --- /dev/null +++ b/libmafw-gst-renderer/gstscreenshot.h @@ -0,0 +1,36 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje + * Portion Copyright © 2009 Nokia Corporation and/or its + * subsidiary(-ies).* All rights reserved. * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __BVW_FRAME_CONV_H__ +#define __BVW_FRAME_CONV_H__ + +#include + +G_BEGIN_DECLS + +typedef void (*BvwFrameConvCb)(GstBuffer *result, gpointer user_data); + +gboolean bvw_frame_conv_convert (GstBuffer *buf, GstCaps *to, + BvwFrameConvCb cb, gpointer cb_data); + +G_END_DECLS + +#endif /* __BVW_FRAME_CONV_H__ */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer-marshal.list b/libmafw-gst-renderer/mafw-gst-renderer-marshal.list new file mode 100644 index 0000000..113502f --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-marshal.list @@ -0,0 +1,2 @@ +# MafwPlaylistIterator::playlist-changed(clip_changed, domain, code, message) +VOID: BOOLEAN, UINT, INT, STRING diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-paused.c b/libmafw-gst-renderer/mafw-gst-renderer-state-paused.c new file mode 100644 index 0000000..742d474 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-paused.c @@ -0,0 +1,379 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "mafw-gst-renderer-state-paused.h" +#include "mafw-gst-renderer-utils.h" +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-state-paused" + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error); +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error); +static void _do_stop(MafwGstRendererState *self, GError **error); +static void _do_resume(MafwGstRendererState *self, GError **error); +static void _do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error); +static void _do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error); +static void _do_previous(MafwGstRendererState *self, GError **error); +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error); +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error); +static void _notify_seek(MafwGstRendererState *self, GError **error); +static void _notify_buffer_status(MafwGstRendererState *self, gdouble percent, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +static GValue* _get_property_value(MafwGstRendererState *self, + const gchar *name); + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +static void _handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point); + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_TYPE(MafwGstRendererStatePaused, mafw_gst_renderer_state_paused, + MAFW_TYPE_GST_RENDERER_STATE); + +static void mafw_gst_renderer_state_paused_init(MafwGstRendererStatePaused *self) +{ +} + +static void mafw_gst_renderer_state_paused_class_init( + MafwGstRendererStatePausedClass *klass) +{ + MafwGstRendererStateClass *state_class; + + state_class = MAFW_GST_RENDERER_STATE_CLASS(klass); + g_return_if_fail(state_class != NULL); + + state_class->name = g_strdup("Paused"); + + /* Playback */ + + state_class->play = _do_play; + state_class->play_object = _do_play_object; + state_class->stop = _do_stop; + state_class->resume = _do_resume; + state_class->set_position = _do_set_position; + state_class->get_position = _do_get_position; + + /* Playlist */ + + state_class->next = _do_next; + state_class->previous = _do_previous; + state_class->goto_index = _do_goto_index; + + /* Notification metadata */ + + state_class->notify_metadata = _notify_metadata; + + /* Notification worker */ + + state_class->notify_play = _notify_play; + /* state_class->notify_pause is not allowed */ + state_class->notify_seek = _notify_seek; + state_class->notify_buffer_status = _notify_buffer_status; + + /* Playlist editing signals */ + + state_class->playlist_contents_changed = + _playlist_contents_changed; + + /* Property methods */ + + state_class->get_property_value = _get_property_value; + + /* Memory card event handlers */ + + state_class->handle_pre_unmount = _handle_pre_unmount; +} + +GObject *mafw_gst_renderer_state_paused_new(MafwGstRenderer *renderer) +{ + MafwGstRendererState *state; + + state = MAFW_GST_RENDERER_STATE( + g_object_new(MAFW_TYPE_GST_RENDERER_STATE_PAUSED, NULL)); + state->renderer = renderer; + + return G_OBJECT(state); +} + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + mafw_gst_renderer_state_do_play(self, error); +} + +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error) +{ + MafwGstRendererPlaybackMode cur_mode, prev_mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + + self->renderer->worker->stay_paused = FALSE; + prev_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + mafw_gst_renderer_state_do_play_object(self, object_id, error); + cur_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + + /* If this happens it means that we interrupted playlist playback + so let's resume it when play_object is finished */ + if (cur_mode != prev_mode) { + self->renderer->resume_playlist = TRUE; + } +} + +static void _do_stop(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + + self->renderer->worker->stay_paused = FALSE; + /* Stop playback */ + mafw_gst_renderer_state_do_stop(self, error); +} + +static void _do_resume(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + self->renderer->worker->stay_paused = FALSE; + mafw_gst_renderer_worker_resume(renderer->worker); + + /* Transition will be done after receiving notify_play */ +} + +static void _do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + self->renderer->worker->stay_paused = TRUE; + mafw_gst_renderer_state_do_set_position(self, mode, seconds, error); +} + +static void _do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + mafw_gst_renderer_state_do_get_position(self, seconds, error); +} + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + self->renderer->worker->stay_paused = TRUE; + mafw_gst_renderer_state_do_next(self, error); +} + +static void _do_previous(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + self->renderer->worker->stay_paused = TRUE; + mafw_gst_renderer_state_do_prev(self, error); +} + +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + self->renderer->worker->stay_paused = FALSE; + mafw_gst_renderer_state_do_goto_index(self, index, error); +} + + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + g_debug("running _notify_metadata..."); + /* Kindly Ignore this notification: + probably a delayed (now useless) metadata resolution */ +} + + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Change status to play */ + mafw_gst_renderer_set_state(renderer, Playing); +} + +static void _notify_seek(MafwGstRendererState *self, GError **error) +{ + mafw_gst_renderer_state_do_notify_seek(self, error); +} + +static void _notify_buffer_status(MafwGstRendererState *self, gdouble percent, + GError **error) +{ + mafw_gst_renderer_state_do_notify_buffer_status (self, percent, error); +} + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + MafwGstRendererPlaybackMode mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self)); + + /* Play the new index only if we are not in standalone mode. + Otherwise, when play_object finishes the new item will be + played if that's been suggested with renderer->resume_playlist */ + mode = mafw_gst_renderer_get_playback_mode(self->renderer); + if (clip_changed && mode == MAFW_GST_RENDERER_MODE_PLAYLIST) { + mafw_gst_renderer_state_do_play(self, error); + } +} + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* _get_property_value(MafwGstRendererState *self, const gchar *name) +{ + GValue *value = NULL; + + g_return_val_if_fail(MAFW_IS_GST_RENDERER_STATE_PAUSED(self), value); + + if (!g_strcmp0(name, MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS)) { + gboolean is_seekable = + mafw_gst_renderer_worker_get_seekable( + self->renderer->worker); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_STRING); + if (is_seekable) { + g_value_set_string(value, "seek"); + } else { + g_value_set_string(value, ""); + } + } + + return value; +} + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +static void _handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point) +{ + gchar *mount_uri; + + /* If not playing anything, bail out */ + if (!self->renderer->media->uri) { + return; + } + + /* Check if mount point is URI or path, we need URI */ + if (!g_str_has_prefix(mount_point, "file://")) { + mount_uri = g_filename_to_uri(mount_point, NULL, NULL); + } else { + mount_uri = g_strdup(mount_point); + } + + /* Stop if playing from unmounted location */ + if (g_str_has_prefix(self->renderer->media->uri, mount_uri)) { + g_debug("PAUSED-STATE: stopping to mount card"); + mafw_gst_renderer_stop(MAFW_RENDERER(self->renderer), + NULL, + NULL); + } + + g_free(mount_uri); +} diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-paused.h b/libmafw-gst-renderer/mafw-gst-renderer-state-paused.h new file mode 100644 index 0000000..f0098d8 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-paused.h @@ -0,0 +1,76 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_STATE_PAUSED_H +#define MAFW_GST_RENDERER_STATE_PAUSED_H + + +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-state.h" + +G_BEGIN_DECLS + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER_STATE_PAUSED \ + (mafw_gst_renderer_state_paused_get_type()) +#define MAFW_GST_RENDERER_STATE_PAUSED(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_GST_RENDERER_STATE_PAUSED, \ + MafwGstRendererStatePaused)) +#define MAFW_IS_GST_RENDERER_STATE_PAUSED(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_GST_RENDERER_STATE_PAUSED)) +#define MAFW_GST_RENDERER_STATE_PAUSED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_GST_RENDERER_STATE_PAUSED, \ + MafwGstRendererStatePaused)) +#define MAFW_GST_RENDERER_STATE_PAUSED_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_GST_RENDERER_STATE_PAUSED, \ + MafwGstRendererStatePausedClass)) +#define MAFW_IS_GST_RENDERER_STATE_PAUSED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_GST_RENDERER_STATE_PAUSED)) + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + + +typedef struct _MafwGstRendererStatePaused MafwGstRendererStatePaused; +typedef struct _MafwGstRendererStatePausedClass MafwGstRendererStatePausedClass; + +struct _MafwGstRendererStatePausedClass { + MafwGstRendererStateClass parent_class; +}; + +struct _MafwGstRendererStatePaused { + MafwGstRendererState parent; +}; + +GType mafw_gst_renderer_state_paused_get_type(void); + +GObject *mafw_gst_renderer_state_paused_new(MafwGstRenderer *renderer); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-playing.c b/libmafw-gst-renderer/mafw-gst-renderer-state-playing.c new file mode 100644 index 0000000..bc7bb8c --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-playing.c @@ -0,0 +1,445 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "mafw-gst-renderer-state-playing.h" +#include "mafw-gst-renderer-utils.h" +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-state-playing" + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error); +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error); +static void _do_stop(MafwGstRendererState *self, GError **error); +static void _do_pause(MafwGstRendererState *self, GError **error); +static void _do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error); +static void _do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error); +static void _do_previous(MafwGstRendererState *self, GError **error); +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error); + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error); +static void _notify_pause(MafwGstRendererState *self, GError **error); +static void _notify_seek(MafwGstRendererState *self, GError **error); +static void _notify_buffer_status(MafwGstRendererState *self, gdouble percent, + GError **error); +static void _notify_eos(MafwGstRendererState *self, GError **error); + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +static GValue* _get_property_value(MafwGstRendererState *self, + const gchar *name); + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +static void _handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point); + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_TYPE(MafwGstRendererStatePlaying, mafw_gst_renderer_state_playing, + MAFW_TYPE_GST_RENDERER_STATE); + +static void mafw_gst_renderer_state_playing_init(MafwGstRendererStatePlaying *self) +{ +} + +static void mafw_gst_renderer_state_playing_class_init( + MafwGstRendererStatePlayingClass *klass) +{ + MafwGstRendererStateClass *state_class; + + state_class = MAFW_GST_RENDERER_STATE_CLASS(klass); + g_return_if_fail(state_class != NULL); + + state_class->name = g_strdup("Playing"); + + /* Playback */ + + state_class->play = _do_play; + state_class->play_object = _do_play_object; + state_class->stop = _do_stop; + state_class->pause = _do_pause; + /* state_class->resume is not allowed */ + state_class->set_position = _do_set_position; + state_class->get_position = _do_get_position; + + /* Playlist */ + + state_class->next = _do_next; + state_class->previous = _do_previous; + state_class->goto_index = _do_goto_index; + + /* Notification metadata */ + + state_class->notify_metadata = _notify_metadata; + + /* Notification worker */ + + state_class->notify_play = _notify_play; + state_class->notify_pause = _notify_pause; + state_class->notify_seek = _notify_seek; + state_class->notify_buffer_status = _notify_buffer_status; + state_class->notify_eos = _notify_eos; + + /* Playlist editing signals */ + + state_class->playlist_contents_changed = + _playlist_contents_changed; + + /* Property methods */ + + state_class->get_property_value = _get_property_value; + + /* Memory card event handlers */ + + state_class->handle_pre_unmount = _handle_pre_unmount; +} + +GObject *mafw_gst_renderer_state_playing_new(MafwGstRenderer *renderer) +{ + MafwGstRendererState *state; + + state = MAFW_GST_RENDERER_STATE( + g_object_new(MAFW_TYPE_GST_RENDERER_STATE_PLAYING, NULL)); + state->renderer = renderer; + + return G_OBJECT(state); +} + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_play(self, error); +} + +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error) +{ + MafwGstRendererPlaybackMode cur_mode, prev_mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + + prev_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + mafw_gst_renderer_state_do_play_object(self, object_id, error); + cur_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + + /* If this happens it means that we interrupted playlist playback + so let's resume it when play_object is finished */ + if (cur_mode != prev_mode) { + self->renderer->resume_playlist = TRUE; + } +} + +static void _do_stop(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + + /* Stop playback */ + mafw_gst_renderer_state_do_stop(self, error); +} + +static void _do_pause(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + mafw_gst_renderer_worker_pause(renderer->worker); + + /* Transition will be done when receiving pause + * notification */ +} + +static void _do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_set_position(self, mode, seconds, error); +} + +static void _do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_get_position(self, seconds, error); +} + + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_next(self, error); +} + +static void _do_previous(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_prev(self, error); +} + +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_goto_index(self, index, error); +} + + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + g_debug("running _notify_metadata..."); + /* Kindly Ignore this notification: + probably a delayed (now useless) metadata resolution */ +} + + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + /* Kindly ignore this notification: it's received when seeking + * in a stream */ +} + +static void _notify_pause(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Change status to pause */ + mafw_gst_renderer_set_state(renderer, Paused); +} + +static void _notify_seek(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + mafw_gst_renderer_state_do_notify_seek(self, error); +} + +static void _notify_buffer_status(MafwGstRendererState *self, gdouble percent, + GError **error) +{ + mafw_gst_renderer_state_do_notify_buffer_status (self, percent, error); +} + +static void _notify_eos(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer; + MafwGstRendererMovementResult move_type; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Update playcount */ + if (renderer->update_playcount_id > 0) { + g_source_remove(renderer->update_playcount_id); + mafw_gst_renderer_update_stats(renderer); + } + + /* Notice: playback has already stopped, so calling + * mafw_gst_renderer_stop or mafw_gst_renderer_state_stop + * here is an error. + * To set the renderer state to Stopped use this instead: + * mafw_gst_renderer_set_state(self->renderer, Stopped); + */ + + /* If we are not in playlist mode, switch to it, + otherwise move to the next in the playlist */ + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_worker_stop(self->renderer->worker); + mafw_gst_renderer_set_state(self->renderer, Stopped); + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + + /* Do we have to resume playlist playback? */ + if (renderer->resume_playlist) { + mafw_gst_renderer_state_play(self, error); + } + } else { + /* Move to next in playlist */ + move_type = + mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_NEXT, + 0, error); + + switch (move_type) { + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + mafw_gst_renderer_state_play(self, error); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + mafw_gst_renderer_worker_stop(self->renderer->worker); + mafw_gst_renderer_set_state(self->renderer, Stopped); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + break; + default: + g_critical("Movement not controlled"); + } + } +} + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + MafwGstRendererPlaybackMode mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self)); + + /* Play the new index only if we are not in standalone mode. + Otherwise, when play_object finishes the new item will be + played if that's been suggested with renderer->resume_playlist */ + mode = mafw_gst_renderer_get_playback_mode(self->renderer); + if (clip_changed && mode == MAFW_GST_RENDERER_MODE_PLAYLIST) { + mafw_gst_renderer_state_do_play(self, error); + } +} + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* _get_property_value(MafwGstRendererState *self, const gchar *name) +{ + GValue *value = NULL; + + g_return_val_if_fail(MAFW_IS_GST_RENDERER_STATE_PLAYING(self), value); + + if (!g_strcmp0(name, MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS)) { + gboolean is_seekable = + mafw_gst_renderer_worker_get_seekable( + self->renderer->worker); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_STRING); + if (is_seekable) { + g_value_set_string(value, "seek"); + } else { + g_value_set_string(value, ""); + } + } + + return value; +} + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +static void _handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point) +{ + gchar *mount_uri; + + /* If not playing anything, bail out */ + if (!self->renderer->media->uri) { + return; + } + + /* Check if mount point is URI or path, we need URI */ + if (!g_str_has_prefix(mount_point, "file://")) { + mount_uri = g_filename_to_uri(mount_point, NULL, NULL); + } else { + mount_uri = g_strdup(mount_point); + } + + /* Stop if playing from unmounted location */ + if (g_str_has_prefix(self->renderer->media->uri, mount_uri)) { + mafw_gst_renderer_stop(MAFW_RENDERER(self->renderer), + NULL, + NULL); + } + + g_free(mount_uri); +} diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-playing.h b/libmafw-gst-renderer/mafw-gst-renderer-state-playing.h new file mode 100644 index 0000000..0127edb --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-playing.h @@ -0,0 +1,76 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_STATE_PLAYING_H +#define MAFW_GST_RENDERER_STATE_PLAYING_H + + +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-state.h" + +G_BEGIN_DECLS + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER_STATE_PLAYING \ + (mafw_gst_renderer_state_playing_get_type()) +#define MAFW_GST_RENDERER_STATE_PLAYING(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_GST_RENDERER_STATE_PLAYING, \ + MafwGstRendererStatePlaying)) +#define MAFW_IS_GST_RENDERER_STATE_PLAYING(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_GST_RENDERER_STATE_PLAYING)) +#define MAFW_GST_RENDERER_STATE_PLAYING_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_GST_RENDERER_STATE_PLAYING, \ + MafwGstRendererStatePlaying)) +#define MAFW_GST_RENDERER_STATE_PLAYING_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_GST_RENDERER_STATE_PLAYING, \ + MafwGstRendererStatePlayingClass)) +#define MAFW_IS_GST_RENDERER_STATE_PLAYING_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_GST_RENDERER_STATE_PLAYING)) + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + + +typedef struct _MafwGstRendererStatePlaying MafwGstRendererStatePlaying; +typedef struct _MafwGstRendererStatePlayingClass MafwGstRendererStatePlayingClass; + +struct _MafwGstRendererStatePlayingClass { + MafwGstRendererStateClass parent_class; +}; + +struct _MafwGstRendererStatePlaying { + MafwGstRendererState parent; +}; + +GType mafw_gst_renderer_state_playing_get_type(void); + +GObject *mafw_gst_renderer_state_playing_new(MafwGstRenderer *renderer); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.c b/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.c new file mode 100644 index 0000000..3b46057 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.c @@ -0,0 +1,319 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "mafw-gst-renderer-state-stopped.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-state-stopped" + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error); +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error); +static void _do_stop(MafwGstRendererState *self, GError **error); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error); +static void _do_previous(MafwGstRendererState *self, GError **error); +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error); + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +static GValue* _get_property_value(MafwGstRendererState *self, + const gchar *name); + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_TYPE(MafwGstRendererStateStopped, mafw_gst_renderer_state_stopped, + MAFW_TYPE_GST_RENDERER_STATE); + +static void mafw_gst_renderer_state_stopped_init(MafwGstRendererStateStopped *self) +{ +} + +static void mafw_gst_renderer_state_stopped_class_init( + MafwGstRendererStateStoppedClass *klass) +{ + MafwGstRendererStateClass *state_klass; + + state_klass = MAFW_GST_RENDERER_STATE_CLASS(klass); + g_return_if_fail(state_klass != NULL); + + state_klass->name = g_strdup("Stopped"); + + /* Playback */ + + state_klass->play = _do_play; + state_klass->play_object = _do_play_object; + state_klass->stop = _do_stop; + + /* Playlist */ + + state_klass->next = _do_next; + state_klass->previous = _do_previous; + state_klass->goto_index = _do_goto_index; + + /* Metadata */ + + state_klass->notify_metadata = _notify_metadata; + + /* Playlist editing signals */ + + state_klass->playlist_contents_changed = + _playlist_contents_changed; + + /* Property methods */ + + state_klass->get_property_value = _get_property_value; +} + +GObject *mafw_gst_renderer_state_stopped_new(MafwGstRenderer *renderer) +{ + MafwGstRendererState *state; + + state = MAFW_GST_RENDERER_STATE( + g_object_new(MAFW_TYPE_GST_RENDERER_STATE_STOPPED, NULL)); + state->renderer = renderer; + + return G_OBJECT(state); +} + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + mafw_gst_renderer_state_do_play(self, error); +} + +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error) +{ + MafwGstRendererPlaybackMode cur_mode, prev_mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + + prev_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + mafw_gst_renderer_state_do_play_object(self, object_id, error); + cur_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + + /* If this happens it means that we interrupted playlist mode + but we did so in Stopped state, so when play_object finishes + we want to stay Stopped */ + if (cur_mode != prev_mode) { + self->renderer->resume_playlist = FALSE; + } +} + +static void _do_stop(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + /* We are already in Stopped state, so do nothing */ +} + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer = NULL; + MafwGstRendererMovementResult value = MAFW_GST_RENDERER_MOVE_RESULT_OK; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + value = mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_NEXT, + 0, error); + + switch (value) { + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error (error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + mafw_playlist_iterator_reset(renderer->iterator, NULL); + mafw_gst_renderer_set_media_playlist(renderer); + break; + default: + g_critical("Movement not controlled"); + } +} + +static void _do_previous(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer = NULL; + MafwGstRendererMovementResult value = MAFW_GST_RENDERER_MOVE_RESULT_OK; + + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + value = mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_PREV, + 0, error); + + switch (value) { + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + + mafw_playlist_iterator_move_to_last(renderer->iterator, NULL); + mafw_gst_renderer_set_media_playlist(renderer); + break; + default: + g_critical("Movement not controlled"); + } +} + +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + MafwGstRenderer *renderer = NULL; + MafwGstRendererMovementResult value = MAFW_GST_RENDERER_MOVE_RESULT_OK; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + value = mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_INDEX, + index, error); + + switch (value) { + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_INDEX_OUT_OF_BOUNDS, + "Index is out of bounds"); + break; + default: + g_critical("Movement not controlled"); + } +} + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + g_debug("running _notify_metadata..."); + /* This happens because we issued a play() command, this moved us to + Transitioning state, waiting for the URL of the objectid to play, + but before we got the URL and moved to Playing state, a stop() + command was issued. Now we got the results of the stopped play() + command, so we just ignore the result and stay in Stopped state. */ + +} + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self)); + + /* Do nothing, we just stay in Stopped state in any case */ +} + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* _get_property_value(MafwGstRendererState *self, const gchar *name) +{ + GValue *value = NULL; + + g_return_val_if_fail(MAFW_IS_GST_RENDERER_STATE_STOPPED(self), value); + + if (!g_strcmp0(name, MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS)) { + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, ""); + } + + return value; +} diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.h b/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.h new file mode 100644 index 0000000..106ff33 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-stopped.h @@ -0,0 +1,76 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_STATE_STOPPED_H +#define MAFW_GST_RENDERER_STATE_STOPPED_H + + +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-state.h" + +G_BEGIN_DECLS + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER_STATE_STOPPED \ + (mafw_gst_renderer_state_stopped_get_type()) +#define MAFW_GST_RENDERER_STATE_STOPPED(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_GST_RENDERER_STATE_STOPPED, \ + MafwGstRendererStateStopped)) +#define MAFW_IS_GST_RENDERER_STATE_STOPPED(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_GST_RENDERER_STATE_STOPPED)) +#define MAFW_GST_RENDERER_STATE_STOPPED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_GST_RENDERER_STATE_STOPPED, \ + MafwGstRendererStateStopped)) +#define MAFW_GST_RENDERER_STATE_STOPPED_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_GST_RENDERER_STATE_STOPPED, \ + MafwGstRendererStateStoppedClass)) +#define MAFW_IS_GST_RENDERER_STATE_STOPPED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_GST_RENDERER_STATE_STOPPED)) + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + + +typedef struct _MafwGstRendererStateStopped MafwGstRendererStateStopped; +typedef struct _MafwGstRendererStateStoppedClass MafwGstRendererStateStoppedClass; + +struct _MafwGstRendererStateStoppedClass { + MafwGstRendererStateClass parent_class; +}; + +struct _MafwGstRendererStateStopped { + MafwGstRendererState parent; +}; + +GType mafw_gst_renderer_state_stopped_get_type(void); + +GObject *mafw_gst_renderer_state_stopped_new(MafwGstRenderer *renderer); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.c b/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.c new file mode 100644 index 0000000..fdf8a17 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.c @@ -0,0 +1,414 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "mafw-gst-renderer-state-transitioning.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-state-transitioning" + +#define UPDATE_DELAY 10 + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error); +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error); +static void _do_pause(MafwGstRendererState *self, GError **error); +static void _do_stop(MafwGstRendererState *self, GError **error); +static void _do_resume(MafwGstRendererState *self, GError **error); +static void _do_get_position(MafwGstRendererState *self, gint *seconds, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error); +static void _do_previous(MafwGstRendererState *self,GError **error); +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error); + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error); +static void _notify_pause(MafwGstRendererState *self,GError **error); + +static void _notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +static GValue* _get_property_value(MafwGstRendererState *self, + const gchar *name); + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_TYPE(MafwGstRendererStateTransitioning, + mafw_gst_renderer_state_transitioning, + MAFW_TYPE_GST_RENDERER_STATE); + +static void mafw_gst_renderer_state_transitioning_init( + MafwGstRendererStateTransitioning *self) +{ +} + +static void mafw_gst_renderer_state_transitioning_class_init( + MafwGstRendererStateTransitioningClass *klass) +{ + MafwGstRendererStateClass *state_klass ; + + state_klass = MAFW_GST_RENDERER_STATE_CLASS(klass); + g_return_if_fail(state_klass != NULL); + + state_klass->name = g_strdup("Transitioning"); + + /* Playback */ + + state_klass->play = _do_play; + state_klass->play_object = _do_play_object; + state_klass->stop = _do_stop; + state_klass->pause = _do_pause; + state_klass->resume = _do_resume; + state_klass->get_position = _do_get_position; + + /* Playlist */ + + state_klass->next = _do_next; + state_klass->previous = _do_previous; + state_klass->goto_index = _do_goto_index; + + /* Metadata */ + + state_klass->notify_metadata = _notify_metadata; + + /* Notification worker */ + + state_klass->notify_play = _notify_play; + state_klass->notify_pause = _notify_pause; + state_klass->notify_buffer_status = _notify_buffer_status; + + /* Playlist editing signals */ + + state_klass->playlist_contents_changed = + _playlist_contents_changed; + + /* Property methods */ + + state_klass->get_property_value = _get_property_value; +} + +GObject *mafw_gst_renderer_state_transitioning_new(MafwGstRenderer *renderer) +{ + MafwGstRendererState *state; + + state = MAFW_GST_RENDERER_STATE( + g_object_new(MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING, NULL)); + state->renderer = renderer; + + return G_OBJECT(state); +} + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _do_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + mafw_gst_renderer_state_do_play(self, error); +} + +static void _do_play_object(MafwGstRendererState *self, const gchar *object_id, + GError **error) +{ + MafwGstRendererPlaybackMode cur_mode, prev_mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + prev_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + mafw_gst_renderer_state_do_play_object(self, object_id, error); + cur_mode = mafw_gst_renderer_get_playback_mode(self->renderer); + + /* If this happens it means that we interrupted playlist playback + so let's resume it when play_object is finished */ + if (cur_mode != prev_mode) { + self->renderer->resume_playlist = TRUE; + } +} + +static void _do_stop(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + /* Stop playback */ + mafw_gst_renderer_state_do_stop(self, error); +} + +static void _do_pause(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + g_debug("Got pause while transitioning"); + self->renderer->worker->stay_paused = TRUE; +} + +static void _do_resume(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + if (self->renderer->worker->stay_paused) { + g_debug("Got resume while transitioning/paused"); + self->renderer->worker->stay_paused = FALSE; + } else { + g_set_error(error, MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_CANNOT_PLAY, + "cannot resume in transitioning state without " + "having paused before"); + } +} + +static void _do_get_position(MafwGstRendererState *self, gint *seconds, + GError **error) +{ + *seconds = 0; +} + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void _do_next(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + mafw_gst_renderer_state_do_next(self, error); +} + +static void _do_previous(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + mafw_gst_renderer_state_do_prev(self, error); +} + +static void _do_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + mafw_gst_renderer_state_do_goto_index(self, index, error); +} + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + MafwGstRenderer *renderer; + GValue *mval; + gpointer value; + gint nuris, i; + gchar **uris; + gchar *uri; + + g_debug("running _notify_metadata..."); + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* If we have received metadata for the item that we are playing + then play it */ + if (object_id && renderer->media->object_id && + !strcmp(object_id, renderer->media->object_id)) { + /* Check how many uris provide the object_id */ + value = g_hash_table_lookup(metadata, MAFW_METADATA_KEY_URI); + nuris = mafw_metadata_nvalues(value); + if (nuris == 1) { + mval = mafw_metadata_first(metadata, + MAFW_METADATA_KEY_URI); + g_assert(mval); + g_free(renderer->media->uri); + renderer->media->uri = + g_strdup(g_value_get_string(mval)); + uri = renderer->media->uri; + } else if (nuris > 1) { + uris = g_new0(gchar *, nuris + 1); + for (i = 0; i < nuris; i++) { + mval = g_value_array_get_nth(value, i); + uris[i] = (gchar *) g_value_get_string(mval); + } + + /* Try the first URI, if that fails to play back another + * one will be selected until we get a successful one or + * all failed. On success, the selected URI will be + * emitted as metadata */ + g_free(renderer->media->uri); + renderer->media->uri = g_strdup(uris[0]); + } else { + g_assert_not_reached(); + } + + /* Set seekability property; currently, if several uris are + * provided it uses the value of the first uri. If later another + * uri is actually played, then this value should be changed. */ + mval = mafw_metadata_first(metadata, + MAFW_METADATA_KEY_IS_SEEKABLE); + if (mval != NULL) { + renderer->media->seekability = + g_value_get_boolean(mval) ? + SEEKABILITY_SEEKABLE : SEEKABILITY_NO_SEEKABLE; + g_debug("_notify_metadata: source seekability %d", + renderer->media->seekability); + } else { + renderer->media->seekability = SEEKABILITY_UNKNOWN; + g_debug("_notify_metadata: source seekability unknown"); + } + + /* Check for source duration to keep it updated if needed */ + mval = mafw_metadata_first(metadata, + MAFW_METADATA_KEY_DURATION); + + if (mval != NULL) { + renderer->media->duration = g_value_get_int(mval); + g_debug("_notify_metadata: source duration %d", + renderer->media->duration); + } else { + renderer->media->duration = -1; + g_debug("_notify_metadata: source duration unknown"); + } + + /* Play the available uri(s) */ + if (nuris == 1) { + mafw_gst_renderer_worker_play(renderer->worker, uri); + } else { + mafw_gst_renderer_worker_play_alternatives( + renderer->worker, uris); + g_free(uris); + } + } +} + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + if (renderer->media->object_id) + { + renderer->update_playcount_id = g_timeout_add_seconds( + UPDATE_DELAY, + mafw_gst_renderer_update_stats, + renderer); + } + + mafw_gst_renderer_set_state(renderer, Playing); +} + +static void _notify_pause(MafwGstRendererState *self, GError **error) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + MafwGstRenderer *renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + self->renderer->worker->stay_paused = FALSE; + mafw_gst_renderer_set_state(renderer, Paused); +} + +static void _notify_buffer_status(MafwGstRendererState *self, gdouble percent, + GError **error) +{ + mafw_gst_renderer_state_do_notify_buffer_status (self, percent, error); +} + +/*---------------------------------------------------------------------------- + Playlist editing signals + ----------------------------------------------------------------------------*/ + +static void _playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + MafwGstRendererPlaybackMode mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self)); + + /* Play the new index only if we are not in standalone mode. + Otherwise, when play_object finishes the new item will be + played if that's been suggested with renderer->resume_playlist */ + mode = mafw_gst_renderer_get_playback_mode(self->renderer); + if (clip_changed && mode == MAFW_GST_RENDERER_MODE_PLAYLIST) { + mafw_gst_renderer_state_do_play(self, error); + } +} + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* _get_property_value(MafwGstRendererState *self, const gchar *name) +{ + GValue *value = NULL; + + g_return_val_if_fail(MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(self), + value); + + if (!g_strcmp0(name, MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS)) { + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, ""); + } + + return value; +} diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.h b/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.h new file mode 100644 index 0000000..4530a4f --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state-transitioning.h @@ -0,0 +1,82 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_STATE_TRANSITIONING_H +#define MAFW_GST_RENDERER_STATE_TRANSITIONING_H + + +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-state.h" +#include "mafw-gst-renderer-utils.h" + +G_BEGIN_DECLS + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING \ + (mafw_gst_renderer_state_transitioning_get_type()) +#define MAFW_GST_RENDERER_STATE_(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING, \ + MafwGstRendererStateTransitioning)) +#define MAFW_IS_GST_RENDERER_STATE_TRANSITIONING(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING)) +#define MAFW_GST_RENDERER_STATE_TRANSITIONING_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING, \ + MafwGstRendererStateTransitioning)) +#define MAFW_GST_RENDERER_STATE_TRANSITIONING_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING, \ + MafwGstRendererStateTransitioningClass)) +#define MAFW_IS_GST_RENDERER_STATE_TRANSITIONING_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MAFW_TYPE_GST_RENDERER_STATE_TRANSITIONING)) + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + + +typedef struct _MafwGstRendererStateTransitioning MafwGstRendererStateTransitioning; +typedef struct _MafwGstRendererStateTransitioningClass MafwGstRendererStateTransitioningClass; + +struct _MafwGstRendererStateTransitioningClass { + MafwGstRendererStateClass parent_class; +}; + +struct _MafwGstRendererStateTransitioning { + MafwGstRendererState parent; +}; + +GType mafw_gst_renderer_state_transitioning_get_type(void); + +GObject *mafw_gst_renderer_state_transitioning_new(MafwGstRenderer *renderer); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state.c b/libmafw-gst-renderer/mafw-gst-renderer-state.c new file mode 100644 index 0000000..274fcec --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state.c @@ -0,0 +1,825 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-state.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-state" + +/*---------------------------------------------------------------------------- + Default playback implementations + ----------------------------------------------------------------------------*/ + +static void _default_play(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_PLAY, + "Play: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + + +static void _default_play_object(MafwGstRendererState *self, + const gchar *objectid, + GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_PLAY, + "Play object: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_stop(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_STOP, + "Stop: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_pause(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_PAUSE, + "Pause: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_resume(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_PLAY, + "Resume: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_set_position (MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_PLAY, + "Set position: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_get_position (MafwGstRendererState *self, + gint *seconds, + GError **error) +{ + g_set_error(error, MAFW_RENDERER_ERROR, MAFW_RENDERER_ERROR_CANNOT_GET_POSITION, + "Get position: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + Default playlist implementations + ----------------------------------------------------------------------------*/ + +static void _default_next(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_EXTENSION_ERROR, MAFW_EXTENSION_ERROR_FAILED, + "Next: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_previous(MafwGstRendererState *self, GError **error) +{ + g_set_error(error, MAFW_EXTENSION_ERROR, MAFW_EXTENSION_ERROR_FAILED, + "Previous: Operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + g_set_error(error, MAFW_EXTENSION_ERROR, MAFW_EXTENSION_ERROR_FAILED, + "Goto index: operation not allowed in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + Default notify metadata implementation + ----------------------------------------------------------------------------*/ + +static void _default_notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + + g_critical("Notify metadata: got unexpected metadata in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + Default notify worker implementations + ----------------------------------------------------------------------------*/ + +static void _default_notify_play(MafwGstRendererState *self, GError **error) +{ + g_critical("Notify play: unexpected Play notification received in %s " + "state", MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_notify_pause(MafwGstRendererState *self, GError **error) +{ + + g_critical("Notify pause: unexpected Pause notification received %s " + "state", MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_notify_seek(MafwGstRendererState *self, GError **error) +{ + g_critical("Notify seek: incorrect operation in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +static void _default_notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error) +{ + g_critical("Notify buffer status: incorrect operation in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + + +static void _default_notify_eos(MafwGstRendererState *self, GError **error) +{ + g_critical("Notify eos: incorrect operation in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + Default playlist editing signal handlers implementation + ----------------------------------------------------------------------------*/ + +static void _default_playlist_contents_changed(MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + g_warning("playlist::contents-changed not implemented in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + Default property methods implementation + ----------------------------------------------------------------------------*/ + +static GValue* _default_get_property_value(MafwGstRendererState *self, + const gchar *name) +{ + g_warning("get_property_value function not implemented in %s state", + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); + return NULL; +} + +/*---------------------------------------------------------------------------- + Default memory card event handlers implementation + ----------------------------------------------------------------------------*/ + +static void _default_handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point) +{ + g_debug("pre-unmount signal received: %s in state %s", mount_point, + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->name); +} + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_ABSTRACT_TYPE(MafwGstRendererState, mafw_gst_renderer_state, + G_TYPE_OBJECT); + +static void mafw_gst_renderer_state_init(MafwGstRendererState *self) +{ +} + +static void mafw_gst_renderer_state_class_init(MafwGstRendererStateClass *klass) +{ + /* Playback */ + + klass->play = _default_play; + klass->play_object = _default_play_object; + klass->stop = _default_stop; + klass->pause = _default_pause; + klass->resume = _default_resume; + klass->set_position = _default_set_position; + klass->get_position = _default_get_position; + + /* Playlist */ + + klass->next = _default_next; + klass->previous = _default_previous; + klass->goto_index = _default_goto_index; + + /* Notification metadata */ + + klass->notify_metadata = _default_notify_metadata; + + /* Notification worker */ + + klass->notify_play = _default_notify_play; + klass->notify_pause = _default_notify_pause; + klass->notify_seek = _default_notify_seek; + klass->notify_buffer_status = _default_notify_buffer_status; + klass->notify_eos = _default_notify_eos; + + klass->notify_eos = _default_notify_eos; + + /* Playlist editing signals */ + + klass->playlist_contents_changed = + _default_playlist_contents_changed; + + /* Property methods */ + + klass->get_property_value = _default_get_property_value; + + /* Memory card event handlers */ + + klass->handle_pre_unmount = _default_handle_pre_unmount; +} + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_play(MafwGstRendererState *self, GError **error) + +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->play(self, error); +} + +void mafw_gst_renderer_state_play_object(MafwGstRendererState *self, + const gchar *object_id, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->play_object(self, object_id, + error); +} + +void mafw_gst_renderer_state_stop(MafwGstRendererState *self, GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->stop(self, error); +} + +void mafw_gst_renderer_state_pause(MafwGstRendererState *self, GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->pause(self, error); +} + +void mafw_gst_renderer_state_resume(MafwGstRendererState *self, GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->resume(self, error); +} + +void mafw_gst_renderer_state_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->set_position(self, mode, seconds, + error); +} + +void mafw_gst_renderer_state_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->get_position(self, seconds, + error); +} + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_next(MafwGstRendererState *self, GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->next(self, error); +} + +void mafw_gst_renderer_state_previous(MafwGstRendererState *self, GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->previous(self, error); +} + +void mafw_gst_renderer_state_goto_index(MafwGstRendererState *self, guint index, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->goto_index(self, index, error); + +} + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_metadata(self, object_id, + metadata, + error); +} + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_notify_play(MafwGstRendererState *self, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_play(self, error); +} + +void mafw_gst_renderer_state_notify_pause(MafwGstRendererState *self, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_pause(self, error); +} + +void mafw_gst_renderer_state_notify_seek(MafwGstRendererState *self, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_seek(self, error); +} + +void mafw_gst_renderer_state_notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_buffer_status(self, + percent, + error); +} + +void mafw_gst_renderer_state_notify_eos(MafwGstRendererState *self, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->notify_eos(self, error); +} + +/*---------------------------------------------------------------------------- + Playlist editing handlers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_playlist_contents_changed_handler( + MafwGstRendererState *self, + gboolean clip_changed, + GError **error) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)->playlist_contents_changed( + self, + clip_changed, + error); +} + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* mafw_gst_renderer_state_get_property_value(MafwGstRendererState *self, + const gchar *name) +{ + return MAFW_GST_RENDERER_STATE_GET_CLASS(self)->get_property_value( + self, + name); +} + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point) +{ + MAFW_GST_RENDERER_STATE_GET_CLASS(self)-> + handle_pre_unmount(self, mount_point); +} + +/*---------------------------------------------------------------------------- + Helpers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_do_play(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer; + GError *gm_error = NULL; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Stop any on going playback */ + mafw_gst_renderer_worker_stop(renderer->worker); + + /* Play command only affects playlists, so switch to playlist + mode first if necessary */ + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + } + + /* Do we have any objectid to play? Otherwise we cannot do it */ + if (renderer->media->object_id) { + /* If so, resolve URI for this objectid */ + mafw_gst_renderer_get_metadata(renderer, + renderer->media->object_id, + &gm_error); + if (gm_error) { + MafwGstRendererErrorClosure *error_closure; + if (error) { + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "Unable to find media"); + } + + /* This is a playback error: execute error policy */ + error_closure = g_new0(MafwGstRendererErrorClosure, 1); + error_closure->renderer = renderer; + error_closure->error = g_error_copy(gm_error); + g_idle_add(mafw_gst_renderer_manage_error_idle, + error_closure); + + g_error_free(gm_error); + } else { + mafw_gst_renderer_set_state(renderer, Transitioning); + } + } else if (error) { + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no media to play"); + mafw_gst_renderer_set_state(renderer, Stopped); + } +} + +void mafw_gst_renderer_state_do_play_object(MafwGstRendererState *self, + const gchar *object_id, + GError **error) +{ + MafwGstRenderer *renderer; + GError *gm_error = NULL; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Stop any ongoing playback */ + mafw_gst_renderer_worker_stop(renderer->worker); + + if (object_id) { + /* Switch to standalone mode */ + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_STANDALONE); + + mafw_gst_renderer_set_object(renderer, object_id); + mafw_gst_renderer_get_metadata(renderer, + renderer->media->object_id, + &gm_error); + if (gm_error) { + MafwGstRendererErrorClosure *error_closure; + if (error) { + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "Unable to find media"); + } + + /* This is a playback error: execute error policy */ + error_closure = g_new0(MafwGstRendererErrorClosure, 1); + error_closure->renderer = renderer; + error_closure->error = g_error_copy(gm_error); + g_idle_add(mafw_gst_renderer_manage_error_idle, + error_closure); + g_error_free(gm_error); + } else { + /* Play object has been successful */ + mafw_gst_renderer_set_state(renderer, Transitioning); + } + } else if (error) { + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no media to play"); + mafw_gst_renderer_set_state(renderer, Stopped); + } +} + +void mafw_gst_renderer_state_do_stop(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* Stop any ongoing playback */ + mafw_gst_renderer_worker_stop(renderer->worker); + + /* Cancel update */ + if (renderer->update_playcount_id > 0) { + g_source_remove(renderer->update_playcount_id); + renderer->update_playcount_id = 0; + } + + /* Set new state */ + mafw_gst_renderer_set_state(renderer, Stopped); + + /* If we were playing a standalone object, then go back + to playlist mode and stay stopped */ + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + } +} + +void mafw_gst_renderer_state_do_next (MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer; + MafwGstRendererMovementResult move_type; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* If we are in standalone mode, we switch back to playlist + * mode. Then we resume playback only if renderer->resume_playlist + * was set. + * If we are in playlist mode we just move to the next and + * play. + */ + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + } + + move_type = mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_NEXT, + 0, error); + switch (move_type) { + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + if (mode == MAFW_GST_RENDERER_MODE_PLAYLIST || + renderer->resume_playlist) { + /* We issued the comand in playlist mode, or + in standalone mode but with resume_playlist + set, so let's play the new item */ + mafw_gst_renderer_state_play(self, error); + + } else { + /* We issued the command in standalone mode and we + do not want to resume playlist, so let's + move to Stopped */ + mafw_gst_renderer_state_stop(self, NULL); + } + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + mafw_gst_renderer_state_stop(self, NULL); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + /* Normal mode */ + mafw_playlist_iterator_reset(renderer->iterator, NULL); + mafw_gst_renderer_set_media_playlist(renderer); + mafw_gst_renderer_state_play(self, error); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + break; + default: + g_critical("Movement not controlled"); + } +} + +void mafw_gst_renderer_state_do_prev(MafwGstRendererState *self, GError **error) +{ + MafwGstRenderer *renderer; + MafwGstRendererMovementResult move_type; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + } + + move_type = mafw_gst_renderer_move(renderer, + MAFW_GST_RENDERER_MOVE_TYPE_PREV, + 0, error); + switch (move_type) { + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + if (mode == MAFW_GST_RENDERER_MODE_PLAYLIST || + renderer->resume_playlist) { + /* We issued the comand in playlist mode, or + in standalone mode but with resume_playlist + set, so let's play the new item */ + mafw_gst_renderer_state_play(self, error); + + } else { + /* We issued the command in standalone mode and we + do not want to resume playlist, so let's + move to Stopped */ + mafw_gst_renderer_state_stop(self, NULL); + } + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + mafw_gst_renderer_state_stop(self, NULL); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + /* Normal mode */ + mafw_playlist_iterator_move_to_last(renderer->iterator, NULL); + mafw_gst_renderer_set_media_playlist(renderer); + mafw_gst_renderer_state_play(self, error); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + break; + default: + g_critical("Movement not controlled"); + } +} + + +void mafw_gst_renderer_state_do_goto_index(MafwGstRendererState *self, + guint index, + GError **error) +{ + MafwGstRenderer *renderer; + MafwGstRendererMovementResult move_type; + MafwGstRendererPlaybackMode mode; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* If we are in standalone mode, we switch back to playlist + * mode. Then we resume playback only if renderer->resume_playlist + * was set. + * If we are in playlist mode we just move to the next and + * play. + */ + mode = mafw_gst_renderer_get_playback_mode(renderer); + if (mode == MAFW_GST_RENDERER_MODE_STANDALONE) { + mafw_gst_renderer_set_playback_mode( + renderer, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(renderer); + } + + move_type = mafw_gst_renderer_move(renderer, MAFW_GST_RENDERER_MOVE_TYPE_INDEX, index, error); + + switch (move_type) { + case MAFW_GST_RENDERER_MOVE_RESULT_OK: + if (mode == MAFW_GST_RENDERER_MODE_PLAYLIST || + renderer->resume_playlist) { + /* We issued the comand in playlist mode, or + in standalone mode but with resume_playlist + set, so let's play the new item */ + mafw_gst_renderer_state_play(self, error); + + } else { + /* We issued the command in standalone mode and we + do not want to resume playlist, so let's + move to Stopped */ + mafw_gst_renderer_state_stop(self, NULL); + } + break; + case MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_NO_MEDIA, + "There is no playlist or media to play"); + mafw_gst_renderer_state_stop(self, NULL); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT: + g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_INDEX_OUT_OF_BOUNDS, + "Index is out of bounds"); + mafw_gst_renderer_state_stop(self, NULL); + break; + case MAFW_GST_RENDERER_MOVE_RESULT_ERROR: + break; + default: + g_critical("Movement not controlled"); + } +} + +void mafw_gst_renderer_state_do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error) +{ + *seconds = mafw_gst_renderer_worker_get_position(self->renderer->worker); + if (*seconds < 0) { + *seconds = 0; + g_set_error(error, MAFW_EXTENSION_ERROR, + MAFW_RENDERER_ERROR_CANNOT_GET_POSITION, + "Position query failed"); + } +} + +void mafw_gst_renderer_state_do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, + gint seconds, + GError **error) +{ + MafwGstRenderer *renderer; + GstSeekType seektype; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + /* TODO Gst stuff should be moved to worker, not handled here... */ + if (mode == SeekAbsolute) { + if (seconds < 0) { + seektype = GST_SEEK_TYPE_END; + seconds *= -1; + } else { + seektype = GST_SEEK_TYPE_SET; + } + } else if (mode == SeekRelative) { + seektype = GST_SEEK_TYPE_CUR; + } else { + g_critical("Unknown seek mode: %d", mode); + g_set_error(error, MAFW_EXTENSION_ERROR, + MAFW_EXTENSION_ERROR_INVALID_PARAMS, + "Unknown seek mode: %d", mode); + return; + } + if (renderer->seek_pending) { + g_debug("seek pending, storing position %d", seconds); + renderer->seek_type_pending = seektype; + renderer->seeking_to = seconds; + } else { + renderer->seek_pending = TRUE; + mafw_gst_renderer_worker_set_position(renderer->worker, + seektype, + seconds, + error); + } +} + +void mafw_gst_renderer_state_do_notify_seek(MafwGstRendererState *self, + GError **error) +{ + MafwGstRenderer *renderer; + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + if (renderer->seeking_to != -1) { + renderer->seek_pending = TRUE; + mafw_gst_renderer_worker_set_position(renderer->worker, + renderer->seek_type_pending, + renderer->seeking_to, + NULL); + } else { + renderer->seek_pending = FALSE; + } + renderer->seeking_to = -1; +} + +void mafw_gst_renderer_state_do_notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error) +{ + MafwGstRenderer *renderer = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER_STATE(self)); + + renderer = MAFW_GST_RENDERER_STATE(self)->renderer; + + mafw_renderer_emit_buffering_info(MAFW_RENDERER(renderer), percent / 100.0); +} diff --git a/libmafw-gst-renderer/mafw-gst-renderer-state.h b/libmafw-gst-renderer/mafw-gst-renderer-state.h new file mode 100644 index 0000000..5fd3325 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-state.h @@ -0,0 +1,239 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_STATE_H +#define MAFW_GST_RENDERER_STATE_H + + +#include +#include "mafw-gst-renderer-worker.h" + +/* Solving the cyclic dependencies */ +typedef struct _MafwGstRendererState MafwGstRendererState; +typedef struct _MafwGstRendererStateClass MafwGstRendererStateClass; +#include "mafw-gst-renderer.h" + +G_BEGIN_DECLS + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER_STATE \ + (mafw_gst_renderer_state_get_type()) +#define MAFW_GST_RENDERER_STATE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_GST_RENDERER_STATE, \ + MafwGstRendererState)) +#define MAFW_IS_GST_RENDERER_STATE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_GST_RENDERER_STATE)) +#define MAFW_GST_RENDERER_STATE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_GST_RENDERER_STATE, \ + MafwGstRendererStateClass)) +#define MAFW_GST_RENDERER_STATE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_GST_RENDERER_STATE, \ + MafwGstRendererStateClass)) +#define MAFW_IS_GST_RENDERER_STATE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_GST_RENDERER_STATE)) + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + + +struct _MafwGstRendererStateClass { + GObjectClass parent_class; + const gchar* name; + + /* Playback */ + + void (*play)(MafwGstRendererState *self, GError **error); + void (*play_object)(MafwGstRendererState *self, const gchar *object_id, + GError **error); + void (*stop)(MafwGstRendererState *self, GError **error); + void (*pause)(MafwGstRendererState *self, GError **error); + void (*resume)(MafwGstRendererState *self, GError **error); + void (*set_position) (MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error); + void (*get_position) (MafwGstRendererState *self, + gint *seconds, + GError **error); + + /* Playlist */ + + void (*next)(MafwGstRendererState *self, GError **error); + void (*previous)(MafwGstRendererState *self, GError **error); + void (*goto_index)(MafwGstRendererState *self, guint index, + GError **error); + + /* Notification metadata */ + + void (*notify_metadata)(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + + + /* Notifications */ + + void (*notify_play)(MafwGstRendererState *self, GError **error); + void (*notify_pause)(MafwGstRendererState *self, GError **error); + void (*notify_seek)(MafwGstRendererState *self, GError **error); + void (*notify_buffer_status)(MafwGstRendererState *self, gdouble percent, + GError **error); + void (*notify_eos) (MafwGstRendererState *self, GError **error); + + /* Playlist editing signals */ + + void (*playlist_contents_changed)(MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + /* Property methods */ + + GValue* (*get_property_value)(MafwGstRendererState *self, + const gchar *name); + + /* Memory card event handlers */ + + void (*handle_pre_unmount)(MafwGstRendererState *self, + const gchar *mount_point); +}; + +struct _MafwGstRendererState { + GObject parent; + + MafwGstRenderer *renderer; +}; + +GType mafw_gst_renderer_state_get_type(void); + + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_play(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_play_object(MafwGstRendererState *self, + const gchar *object_id, + GError **error); +void mafw_gst_renderer_state_stop(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_pause(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_resume(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error); +void mafw_gst_renderer_state_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_next(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_previous(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_goto_index(MafwGstRendererState *self, guint index, + GError **error); + + +/*---------------------------------------------------------------------------- + Notification metatada + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_notify_metadata(MafwGstRendererState *self, + const gchar *object_id, + GHashTable *metadata, + GError **error); + +/*---------------------------------------------------------------------------- + Notification worker + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_notify_play(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_notify_pause(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_notify_seek(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error); +void mafw_gst_renderer_state_notify_eos(MafwGstRendererState *self, + GError **error); + +/*---------------------------------------------------------------------------- + Playlist editing handlers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_playlist_contents_changed_handler( + MafwGstRendererState *self, + gboolean clip_changed, + GError **error); + +/*---------------------------------------------------------------------------- + Property methods + ----------------------------------------------------------------------------*/ + +GValue* mafw_gst_renderer_state_get_property_value(MafwGstRendererState *self, + const gchar *name); + +/*---------------------------------------------------------------------------- + Memory card event handlers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_handle_pre_unmount(MafwGstRendererState *self, + const gchar *mount_point); + +/*---------------------------------------------------------------------------- + Helpers + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_state_do_play(MafwGstRendererState *self, GError **error); +void mafw_gst_renderer_state_do_play_object(MafwGstRendererState *self, + const gchar *object_id, + GError **error); +void mafw_gst_renderer_state_do_stop(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_do_next(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_do_prev(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_do_goto_index(MafwGstRendererState *self, + guint index, + GError **error); +void mafw_gst_renderer_state_do_set_position(MafwGstRendererState *self, + MafwRendererSeekMode mode, gint seconds, + GError **error); +void mafw_gst_renderer_state_do_get_position(MafwGstRendererState *self, + gint *seconds, + GError **error); +void mafw_gst_renderer_state_do_notify_seek(MafwGstRendererState *self, + GError **error); +void mafw_gst_renderer_state_do_notify_buffer_status(MafwGstRendererState *self, + gdouble percent, + GError **error); + +G_END_DECLS + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-utils.c b/libmafw-gst-renderer/mafw-gst-renderer-utils.c new file mode 100644 index 0000000..0169862 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-utils.c @@ -0,0 +1,105 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "mafw-gst-renderer-utils.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-utils" + +/** + * convert_utf8: + * @src: string. + * @dst: location for utf8 version of @src. + * + * Tries to convert @src into UTF-8, placing it into @dst. + * + * Returns: TRUE on success. + */ +gboolean convert_utf8(const gchar *src, gchar **dst) +{ + GError *error; + + if (!src) + return FALSE; + if (g_utf8_validate(src, -1, NULL)) { + *dst = g_strdup(src); + return TRUE; + } + error = NULL; + *dst = g_locale_to_utf8(src, -1, NULL, NULL, &error); + if (error) { + g_warning("utf8 conversion failed '%s' (%d: %s)", + src, error->code, error->message); + g_error_free(error); + return FALSE; + } + return TRUE; +} + +gboolean uri_is_playlist(const gchar *uri) { + /* TODO: Return if the uri is a playlist or not, using the mime type + instead of the file extension. */ + if ((g_str_has_suffix(uri, ".pls")) || + (g_str_has_suffix(uri, ".m3u")) || + (g_str_has_suffix(uri, ".smil")) || + (g_str_has_suffix(uri, ".smi")) || + (g_str_has_suffix(uri, ".wpl")) || + (g_str_has_suffix(uri, ".wax")) || + (g_str_has_suffix(uri, ".uni")) || + (g_str_has_suffix(uri, ".ram")) || +/* (g_str_has_suffix(uri, ".ra")) || */ + (g_str_has_suffix(uri, ".asx")) || + (g_str_has_suffix(uri, ".rpm"))) + { + return TRUE; + } + return FALSE; +} + +/** + * uri_is_stream: + * @uri: the URI to be checked. + * + * Check if given URI is a stream (not a local resource). To not depend on + * gnomevfs for this, we assume everything that doesn't start with "file://" is + * a stream. + * + * Returns: TRUE if the URI is not local. + */ +gboolean uri_is_stream(const gchar *uri) +{ + if (uri == NULL) { + return FALSE; + } else { + return !g_str_has_prefix(uri, "file://"); + } +} + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer-utils.h b/libmafw-gst-renderer/mafw-gst-renderer-utils.h new file mode 100644 index 0000000..57bbe81 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-utils.h @@ -0,0 +1,34 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef MAFW_GST_RENDERER_UTILS_H +#define MAFW_GST_RENDERER_UTILS_H + +G_BEGIN_DECLS + +gboolean convert_utf8(const gchar *src, gchar **dst); +gboolean uri_is_playlist(const gchar *uri); +gboolean uri_is_stream(const gchar *uri); + +G_END_DECLS +#endif +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.c b/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.c new file mode 100644 index 0000000..4d17ea9 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.c @@ -0,0 +1,702 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME + +#include +#include +#include +#include + +#include "mafw-gst-renderer-worker-volume.h" +#include "config.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume" + +#define MAFW_GST_RENDERER_WORKER_VOLUME_SERVER NULL + +#define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY "PULSE_PROP_media.role" +#define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX "sink-input-by-media-role:" +#define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE "x-maemo" + +#define MAFW_GST_RENDERER_WORKER_SET_TIMEOUT 200 + + +struct _MafwGstRendererWorkerVolume { + pa_glib_mainloop *mainloop; + pa_context *context; + gdouble pulse_volume; + gboolean pulse_mute; + MafwGstRendererWorkerVolumeChangedCb cb; + gpointer user_data; + MafwGstRendererWorkerVolumeMuteCb mute_cb; + gpointer mute_user_data; + gdouble current_volume; + gboolean current_mute; + gboolean pending_operation; + gdouble pending_operation_volume; + gboolean pending_operation_mute; + guint change_request_id; + pa_operation *pa_operation; +}; + +typedef struct { + MafwGstRendererWorkerVolume *wvolume; + MafwGstRendererWorkerVolumeInitCb cb; + gpointer user_data; +} InitCbClosure; + +#define _pa_volume_to_per_one(volume) \ + ((guint) ((((gdouble)(volume) / (gdouble) PA_VOLUME_NORM) + \ + (gdouble) 0.005) * (gdouble) 100.0) / (gdouble) 100.0) +#define _pa_volume_from_per_one(volume) \ + ((pa_volume_t)((gdouble)(volume) * (gdouble) PA_VOLUME_NORM)) + +#define _pa_operation_running(wvolume) \ + (wvolume->pa_operation != NULL && \ + pa_operation_get_state(wvolume->pa_operation) == PA_OPERATION_RUNNING) + +static void _state_cb_init(pa_context *c, void *data); + + +static gchar *_get_client_name(void) { + gchar buf[PATH_MAX]; + gchar *name = NULL; + + if (pa_get_binary_name(buf, sizeof(buf))) + name = g_strdup_printf("mafw-gst-renderer[%s]", buf); + else + name = g_strdup("mafw-gst-renderer"); + + return name; +} + +static void _ext_stream_restore_read_cb(pa_context *c, + const pa_ext_stream_restore2_info *i, + int eol, + void *userdata) +{ + MafwGstRendererWorkerVolume *wvolume = userdata; + gdouble volume; + gboolean mute; + + if (eol < 0) { + g_critical("eol parameter should not be < 1. " + "Discarding volume event"); + return; + } + + if (i == NULL || + strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX + MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0) { + return; + } + + volume = _pa_volume_to_per_one(pa_cvolume_max(&i->volume)); + mute = i->mute != 0 ? TRUE : FALSE; + + if (_pa_operation_running(wvolume) || + (wvolume->pending_operation && + (wvolume->pending_operation_volume != volume || + wvolume->pending_operation_mute != mute))) { + g_debug("volume notification, but operation running, ignoring"); + return; + } + + wvolume->pulse_volume = volume; + wvolume->pulse_mute = mute; + + /* EMIT VOLUME */ + g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s", + wvolume->pulse_volume, wvolume->pulse_mute, i->name, i->device); + if (!wvolume->pending_operation && + wvolume->pulse_volume != wvolume->current_volume) { + wvolume->current_volume = wvolume->pulse_volume; + if (wvolume->cb != NULL) { + g_debug("signalling volume"); + wvolume->cb(wvolume, wvolume->current_volume, + wvolume->user_data); + } + } + if (!wvolume->pending_operation && + wvolume->pulse_mute != wvolume->current_mute) { + wvolume->current_mute = wvolume->pulse_mute; + if (wvolume->mute_cb != NULL) { + g_debug("signalling mute"); + wvolume->mute_cb(wvolume, wvolume->current_mute, + wvolume->mute_user_data); + } + } + + wvolume->pending_operation = FALSE; +} + +static void _destroy_context(MafwGstRendererWorkerVolume *wvolume) +{ + if (wvolume->pa_operation != NULL) { + if (pa_operation_get_state(wvolume->pa_operation) == + PA_OPERATION_RUNNING) { + pa_operation_cancel(wvolume->pa_operation); + } + pa_operation_unref(wvolume->pa_operation); + wvolume->pa_operation = NULL; + } + pa_context_unref(wvolume->context); +} + +static InitCbClosure *_init_cb_closure_new(MafwGstRendererWorkerVolume *wvolume, + MafwGstRendererWorkerVolumeInitCb cb, + gpointer user_data) +{ + InitCbClosure *closure; + + closure = g_new(InitCbClosure, 1); + closure->wvolume = wvolume; + closure->cb = cb; + closure->user_data = user_data; + + return closure; +} + +static void _connect(gpointer user_data) +{ + gchar *name = NULL; + pa_mainloop_api *api = NULL; + InitCbClosure *closure = user_data; + MafwGstRendererWorkerVolume *wvolume = closure->wvolume; + + name = _get_client_name(); + + /* get the mainloop api and create a context */ + api = pa_glib_mainloop_get_api(wvolume->mainloop); + wvolume->context = pa_context_new(api, name); + g_assert(wvolume->context != NULL); + + /* register some essential callbacks */ + pa_context_set_state_callback(wvolume->context, _state_cb_init, + closure); + + g_debug("connecting to pulse"); + + g_assert(pa_context_connect(wvolume->context, + MAFW_GST_RENDERER_WORKER_VOLUME_SERVER, + PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL, + NULL) >= 0); + g_free(name); +} + +static gboolean _reconnect(gpointer user_data) +{ + InitCbClosure *closure = user_data; + MafwGstRendererWorkerVolume *wvolume = closure->wvolume; + + g_warning("got disconnected from pulse, reconnecting"); + _destroy_context(wvolume); + _connect(user_data); + + return FALSE; +} + +static void +_state_cb(pa_context *c, void *data) +{ + MafwGstRendererWorkerVolume *wvolume = data; + pa_context_state_t state; + + state = pa_context_get_state(c); + + switch (state) { + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + { + InitCbClosure *closure; + + closure = _init_cb_closure_new(wvolume, NULL, NULL); + g_idle_add(_reconnect, closure); + break; + } + case PA_CONTEXT_READY: { + pa_operation *o; + + o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb, + wvolume); + g_assert(o != NULL); + pa_operation_unref(o); + + break; + } + default: + break; + } +} + +static void _ext_stream_restore_read_cb_init(pa_context *c, + const pa_ext_stream_restore2_info *i, + int eol, + void *userdata) +{ + InitCbClosure *closure = userdata; + + if (eol < 0) { + g_critical("eol parameter should not be < 1"); + } + + if (i == NULL || + strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX + MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0) + return; + + closure->wvolume->pulse_volume = + _pa_volume_to_per_one(pa_cvolume_max(&i->volume)); + closure->wvolume->pulse_mute = i->mute != 0 ? TRUE : FALSE; + closure->wvolume->current_volume = closure->wvolume->pulse_volume; + closure->wvolume->current_mute = closure->wvolume->pulse_mute; + + /* NOT EMIT VOLUME, BUT DEBUG */ + g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s", + closure->wvolume->pulse_volume, i->mute, i->name, i->device); + + if (closure->cb != NULL) { + g_debug("initialized: returning volume manager"); + closure->cb(closure->wvolume, closure->user_data); + } else { + if (closure->wvolume->cb != NULL) { + g_debug("signalling volume after reconnection"); + closure->wvolume->cb(closure->wvolume, + closure->wvolume->current_volume, + closure->wvolume->user_data); + } + if (closure->wvolume->mute_cb != NULL) { + g_debug("signalling mute after reconnection"); + closure->wvolume->mute_cb(closure->wvolume, + closure->wvolume-> + current_mute, + closure->wvolume-> + mute_user_data); + } + } + + pa_context_set_state_callback(closure->wvolume->context, _state_cb, + closure->wvolume); + + g_free(closure); +} + +static void _ext_stream_restore_subscribe_cb(pa_context *c, void *userdata) +{ + pa_operation *o; + + o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb, userdata); + g_assert(o != NULL); + pa_operation_unref(o); +} + +static void +_state_cb_init(pa_context *c, void *data) +{ + InitCbClosure *closure = data; + MafwGstRendererWorkerVolume *wvolume = closure->wvolume; + pa_context_state_t state; + + state = pa_context_get_state(c); + + g_debug("state: %d", state); + + switch (state) { + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + g_critical("Connection to pulse failed, reconnection in 1 " + "second"); + g_timeout_add_seconds(1, _reconnect, closure); + break; + case PA_CONTEXT_READY: { + pa_operation *o; + + g_debug("PA_CONTEXT_READY"); + + o = pa_ext_stream_restore2_read(c, + _ext_stream_restore_read_cb_init, + closure); + g_assert(o != NULL); + pa_operation_unref(o); + + pa_ext_stream_restore_set_subscribe_cb( + c, _ext_stream_restore_subscribe_cb, wvolume); + + o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL); + g_assert(o != NULL); + pa_operation_unref(o); + + break; + } + default: + break; + } +} + +static gboolean _destroy_idle(gpointer data) +{ + MafwGstRendererWorkerVolume *wvolume = data; + + g_debug("destroying"); + + _destroy_context(wvolume); + pa_glib_mainloop_free(wvolume->mainloop); + g_free(wvolume); + + return FALSE; +} + +static void +_state_cb_destroy(pa_context *c, void *data) +{ + pa_context_state_t state; + + state = pa_context_get_state(c); + + switch (state) { + case PA_CONTEXT_TERMINATED: + g_idle_add(_destroy_idle, data); + break; + case PA_CONTEXT_FAILED: + g_error("Unexpected problem in volume management"); + break; + default: + break; + } +} + +static void _success_cb(pa_context *c, int success, void *userdata) +{ + if (success == 0) { + g_critical("Setting volume to pulse operation failed"); + } +} + +static void _remove_set_timeout(MafwGstRendererWorkerVolume *wvolume) +{ + if (wvolume->change_request_id != 0) { + g_source_remove(wvolume->change_request_id); + } + wvolume->change_request_id = 0; +} + +static gboolean _set_timeout(gpointer data) +{ + pa_ext_stream_restore2_info info; + pa_ext_stream_restore2_info *infos[1]; + MafwGstRendererWorkerVolume *wvolume = data; + + if (wvolume->pending_operation) { + g_debug("setting volume ignored as there is still a pending " + "operation. Waiting till next iteration"); + } else if (wvolume->pulse_mute != wvolume->current_mute || + wvolume->pulse_volume != wvolume->current_volume) { + + info.name = MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX + MAFW_GST_RENDERER_WORKER_VOLUME_ROLE; + info.channel_map.channels = 1; + info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; + info.device = NULL; + info.volume_is_absolute = TRUE; + infos[0] = &info; + + info.mute = wvolume->current_mute; + pa_cvolume_init(&info.volume); + pa_cvolume_set(&info.volume, info.channel_map.channels, + _pa_volume_from_per_one(wvolume-> + current_volume)); + + g_debug("setting volume to %lf and mute to %d", + wvolume->current_volume, wvolume->current_mute); + + if (wvolume->pa_operation != NULL) { + pa_operation_unref(wvolume->pa_operation); + } + + wvolume->pending_operation = TRUE; + wvolume->pending_operation_volume = wvolume->current_volume; + wvolume->pending_operation_mute = wvolume->current_mute; + + wvolume->pa_operation = pa_ext_stream_restore2_write( + wvolume->context, + PA_UPDATE_REPLACE, + (const pa_ext_stream_restore2_info* + const *)infos, + 1, TRUE, _success_cb, wvolume); + + if (wvolume->pa_operation == NULL) { + g_critical("NULL operation when writing volume to " + "pulse"); + _remove_set_timeout(wvolume); + } + } else { + g_debug("removing volume timeout"); + _remove_set_timeout(wvolume); + } + + return wvolume->change_request_id != 0; +} + +void mafw_gst_renderer_worker_volume_init(GMainContext *main_context, + MafwGstRendererWorkerVolumeInitCb cb, + gpointer user_data, + MafwGstRendererWorkerVolumeChangedCb + changed_cb, + gpointer changed_user_data, + MafwGstRendererWorkerVolumeMuteCb + mute_cb, gpointer mute_user_data) +{ + MafwGstRendererWorkerVolume *wvolume = NULL; + InitCbClosure *closure; + + g_return_if_fail(cb != NULL); + + g_assert(g_setenv(MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY, + MAFW_GST_RENDERER_WORKER_VOLUME_ROLE, FALSE)); + + g_debug("initializing volume manager"); + + wvolume = g_new0(MafwGstRendererWorkerVolume, 1); + + wvolume->pulse_volume = 1.0; + wvolume->pulse_mute = FALSE; + wvolume->cb = changed_cb; + wvolume->user_data = changed_user_data; + wvolume->mute_cb = mute_cb; + wvolume->mute_user_data = mute_user_data; + + wvolume->mainloop = pa_glib_mainloop_new(main_context); + g_assert(wvolume->mainloop != NULL); + + closure = _init_cb_closure_new(wvolume, cb, user_data); + _connect(closure); +} + +void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume, + gdouble volume, gboolean mute) +{ + gboolean signal_volume, signal_mute; + + g_return_if_fail(wvolume != NULL); + g_return_if_fail(pa_context_get_state(wvolume->context) == + PA_CONTEXT_READY); + +#ifndef MAFW_GST_RENDERER_ENABLE_MUTE + mute = FALSE; +#endif + + signal_volume = wvolume->current_volume != volume && + wvolume->cb != NULL; + signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL; + + wvolume->current_volume = volume; + wvolume->current_mute = mute; + + g_debug("volume set: %lf (mute %d)", volume, mute); + + if (signal_volume) { + g_debug("signalling volume"); + wvolume->cb(wvolume, volume, wvolume->user_data); + } + + if (signal_mute) { + g_debug("signalling mute"); + wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data); + } + + if ((signal_mute || signal_volume) && wvolume->change_request_id == 0) { + wvolume->change_request_id = + g_timeout_add(MAFW_GST_RENDERER_WORKER_SET_TIMEOUT, + _set_timeout, wvolume); + + _set_timeout(wvolume); + } +} + +gdouble mafw_gst_renderer_worker_volume_get( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_val_if_fail(wvolume != NULL, 0.0); + + g_debug("getting volume; %lf", wvolume->current_volume); + + return wvolume->current_volume; +} + +gboolean mafw_gst_renderer_worker_volume_is_muted( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_val_if_fail(wvolume != NULL, FALSE); + + g_debug("getting mute; %d", wvolume->current_mute); + + return wvolume->current_mute; +} + +void mafw_gst_renderer_worker_volume_destroy( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_if_fail(wvolume != NULL); + + g_debug("disconnecting"); + + pa_ext_stream_restore_set_subscribe_cb(wvolume->context, NULL, NULL); + pa_context_set_state_callback(wvolume->context, _state_cb_destroy, + wvolume); + pa_context_disconnect(wvolume->context); +} + + + +#else + + +#include "mafw-gst-renderer-worker-volume.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume-fake" + +struct _MafwGstRendererWorkerVolume { + MafwGstRendererWorkerVolumeChangedCb cb; + gpointer user_data; + MafwGstRendererWorkerVolumeMuteCb mute_cb; + gpointer mute_user_data; + gdouble current_volume; + gboolean current_mute; +}; + +typedef struct { + MafwGstRendererWorkerVolume *wvolume; + MafwGstRendererWorkerVolumeInitCb cb; + gpointer user_data; +} InitCbClosure; + +static gboolean _init_cb_closure(gpointer user_data) +{ + InitCbClosure *closure = user_data; + + if (closure->cb != NULL) { + closure->cb(closure->wvolume, closure->user_data); + } + g_free(closure); + + return FALSE; +} + +void mafw_gst_renderer_worker_volume_init(GMainContext *main_context, + MafwGstRendererWorkerVolumeInitCb cb, + gpointer user_data, + MafwGstRendererWorkerVolumeChangedCb + changed_cb, + gpointer changed_user_data, + MafwGstRendererWorkerVolumeMuteCb + mute_cb, gpointer mute_user_data) +{ + MafwGstRendererWorkerVolume *wvolume = NULL; + InitCbClosure *closure; + + g_return_if_fail(cb != NULL); + + g_debug("initializing volume manager"); + + wvolume = g_new0(MafwGstRendererWorkerVolume, 1); + + wvolume->cb = changed_cb; + wvolume->user_data = changed_user_data; + wvolume->mute_cb = mute_cb; + wvolume->mute_user_data = mute_user_data; + wvolume->current_volume = 0.485; + + closure = g_new0(InitCbClosure, 1); + closure->wvolume = wvolume; + closure->cb = cb; + closure->user_data = user_data; + g_idle_add(_init_cb_closure, closure); +} + +void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume, + gdouble volume, gboolean mute) +{ + gboolean signal_volume, signal_mute; + + g_return_if_fail(wvolume != NULL); + +#ifndef MAFW_GST_RENDERER_ENABLE_MUTE + mute = FALSE; +#endif + + signal_volume = wvolume->current_volume != volume && + wvolume->cb != NULL; + signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL; + + wvolume->current_volume = volume; + wvolume->current_mute = mute; + + g_debug("volume set: %lf (mute %d)", volume, mute); + + if (signal_volume) { + g_debug("signalling volume"); + wvolume->cb(wvolume, volume, wvolume->user_data); + } + + if (signal_mute) { + g_debug("signalling mute"); + wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data); + } +} + +gdouble mafw_gst_renderer_worker_volume_get( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_val_if_fail(wvolume != NULL, 0.0); + + g_debug("getting volume; %lf", wvolume->current_volume); + + return wvolume->current_volume; +} + +gboolean mafw_gst_renderer_worker_volume_is_muted( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_val_if_fail(wvolume != NULL, FALSE); + + g_debug("getting mute; %d", wvolume->current_mute); + + return wvolume->current_mute; +} + +void mafw_gst_renderer_worker_volume_destroy( + MafwGstRendererWorkerVolume *wvolume) +{ + g_return_if_fail(wvolume != NULL); + + g_free(wvolume); +} + +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.h b/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.h new file mode 100644 index 0000000..c1e860e --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-worker-volume.h @@ -0,0 +1,64 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_GST_RENDERER_WORKER_VOLUME_H +#define MAFW_GST_RENDERER_WORKER_VOLUME_H + +#include + +typedef struct _MafwGstRendererWorkerVolume MafwGstRendererWorkerVolume; + +typedef void(*MafwGstRendererWorkerVolumeChangedCb)( + MafwGstRendererWorkerVolume *wvolume, gdouble volume, gpointer data); + +typedef void(*MafwGstRendererWorkerVolumeMuteCb)( + MafwGstRendererWorkerVolume *wvolume, gboolean mute, gpointer data); + +typedef void(*MafwGstRendererWorkerVolumeInitCb)( + MafwGstRendererWorkerVolume *volume, gpointer data); + +G_BEGIN_DECLS + +void mafw_gst_renderer_worker_volume_init(GMainContext *main_context, + MafwGstRendererWorkerVolumeInitCb cb, + gpointer user_data, + MafwGstRendererWorkerVolumeChangedCb + changed_cb, + gpointer changed_user_data, + MafwGstRendererWorkerVolumeMuteCb + mute_cb, gpointer mute_user_data); + +void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume, + gdouble volume, gboolean mute); + +gdouble mafw_gst_renderer_worker_volume_get( + MafwGstRendererWorkerVolume *wvolume); +gboolean mafw_gst_renderer_worker_volume_is_muted( + MafwGstRendererWorkerVolume *wvolume); + +void mafw_gst_renderer_worker_volume_destroy( + MafwGstRendererWorkerVolume *wvolume); + +G_END_DECLS +#endif diff --git a/libmafw-gst-renderer/mafw-gst-renderer-worker.c b/libmafw-gst-renderer/mafw-gst-renderer-worker.c new file mode 100644 index 0000000..0cd1ec4 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-worker.c @@ -0,0 +1,2373 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_GDKPIXBUF +#include +#include +#include +#include "gstscreenshot.h" +#endif + +#include +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-worker.h" +#include "mafw-gst-renderer-utils.h" +#include "blanking.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-worker" + +#define MAFW_GST_RENDERER_WORKER_SECONDS_READY 60 +#define MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY 4 + +#define MAFW_GST_MISSING_TYPE_DECODER "decoder" +#define MAFW_GST_MISSING_TYPE_ENCODER "encoder" + +#define MAFW_GST_BUFFER_TIME 600000L +#define MAFW_GST_LATENCY_TIME (MAFW_GST_BUFFER_TIME / 2) + +#define NSECONDS_TO_SECONDS(ns) ((ns)%1000000000 < 500000000?\ + GST_TIME_AS_SECONDS((ns)):\ + GST_TIME_AS_SECONDS((ns))+1) + +/* Private variables. */ +/* Global reference to worker instance, needed for Xerror handler */ +static MafwGstRendererWorker *Global_worker = NULL; + +/* Forward declarations. */ +static void _do_play(MafwGstRendererWorker *worker); +static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type, + gint position, GError **error); +static void _play_pl_next(MafwGstRendererWorker *worker); + +static void _emit_metadatas(MafwGstRendererWorker *worker); + +static void _current_metadata_add(MafwGstRendererWorker *worker, + const gchar *key, GType type, + const gpointer value); + +/* + * Sends @error to MafwGstRenderer. Only call this from the glib main thread, or + * face the consequences. @err is free'd. + */ +static void _send_error(MafwGstRendererWorker *worker, GError *err) +{ + worker->is_error = TRUE; + if (worker->notify_error_handler) + worker->notify_error_handler(worker, worker->owner, err); + g_error_free(err); +} + +/* + * Posts an @error on the gst bus. _async_bus_handler will then pick it up and + * forward to MafwGstRenderer. @err is free'd. + */ +static void _post_error(MafwGstRendererWorker *worker, GError *err) +{ + gst_bus_post(worker->bus, + gst_message_new_error(GST_OBJECT(worker->pipeline), + err, NULL)); + g_error_free(err); +} + +#ifdef HAVE_GDKPIXBUF +typedef struct { + MafwGstRendererWorker *worker; + gchar *metadata_key; + GdkPixbuf *pixbuf; +} SaveGraphicData; + +static gchar *_init_tmp_file(void) +{ + gint fd; + gchar *path = NULL; + + fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.jpeg", &path, NULL); + close(fd); + + return path; +} + +static void _init_tmp_files_pool(MafwGstRendererWorker *worker) +{ + guint8 i; + + worker->tmp_files_pool_index = 0; + + for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) { + worker->tmp_files_pool[i] = NULL; + } +} + +static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker) +{ + guint8 i; + + for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) && + (worker->tmp_files_pool[i] != NULL); i++) { + g_unlink(worker->tmp_files_pool[i]); + g_free(worker->tmp_files_pool[i]); + } +} + +static const gchar *_get_tmp_file_from_pool( + MafwGstRendererWorker *worker) +{ + gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index]; + + if (path == NULL) { + path = _init_tmp_file(); + worker->tmp_files_pool[worker->tmp_files_pool_index] = path; + } + + if (++(worker->tmp_files_pool_index) >= + MAFW_GST_RENDERER_MAX_TMP_FILES) { + worker->tmp_files_pool_index = 0; + } + + return path; +} + +static void _destroy_pixbuf (guchar *pixbuf, gpointer data) +{ + gst_buffer_unref(GST_BUFFER(data)); +} + +static void _emit_gst_buffer_as_graphic_file_cb(GstBuffer *new_buffer, + gpointer user_data) +{ + SaveGraphicData *sgd = user_data; + GdkPixbuf *pixbuf = NULL; + + if (new_buffer != NULL) { + gint width, height; + GstStructure *structure; + + structure = + gst_caps_get_structure(GST_BUFFER_CAPS(new_buffer), 0); + + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + + pixbuf = gdk_pixbuf_new_from_data( + GST_BUFFER_DATA(new_buffer), GDK_COLORSPACE_RGB, + FALSE, 8, width, height, + GST_ROUND_UP_4(3 * width), _destroy_pixbuf, + new_buffer); + + if (sgd->pixbuf != NULL) { + g_object_unref(sgd->pixbuf); + sgd->pixbuf = NULL; + } + } else { + pixbuf = sgd->pixbuf; + } + + if (pixbuf != NULL) { + gboolean save_ok; + GError *error = NULL; + const gchar *filename; + + filename = _get_tmp_file_from_pool(sgd->worker); + + save_ok = gdk_pixbuf_save (pixbuf, filename, "jpeg", &error, + NULL); + + g_object_unref (pixbuf); + + if (save_ok) { + /* Add the info to the current metadata. */ + _current_metadata_add(sgd->worker, sgd->metadata_key, + G_TYPE_STRING, + (const gpointer) filename); + + /* Emit the metadata. */ + mafw_renderer_emit_metadata_string(sgd->worker->owner, + sgd->metadata_key, + (gchar *) filename); + } else { + if (error != NULL) { + g_warning ("%s\n", error->message); + g_error_free (error); + } else { + g_critical("Unknown error when saving pixbuf " + "with GStreamer data"); + } + } + } else { + g_warning("Could not create pixbuf from GstBuffer"); + } + + g_free(sgd->metadata_key); + g_free(sgd); +} + +static void _pixbuf_size_prepared_cb (GdkPixbufLoader *loader, + gint width, gint height, + gpointer user_data) +{ + /* Be sure the image size is reasonable */ + if (width > 512 || height > 512) { + g_debug ("pixbuf: image is too big: %dx%d", width, height); + gdouble ar; + ar = (gdouble) width / height; + if (width > height) { + width = 512; + height = width / ar; + } else { + height = 512; + width = height * ar; + } + g_debug ("pixbuf: scaled image to %dx%d", width, height); + gdk_pixbuf_loader_set_size (loader, width, height); + } +} + +static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker, + GstBuffer *buffer, + const gchar *metadata_key) +{ + GdkPixbufLoader *loader; + GstStructure *structure; + const gchar *mime = NULL; + GError *error = NULL; + + g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer)); + + structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0); + mime = gst_structure_get_name(structure); + + if (g_str_has_prefix(mime, "video/x-raw")) { + gint framerate_d, framerate_n; + GstCaps *to_caps; + SaveGraphicData *sgd; + + gst_structure_get_fraction (structure, "framerate", + &framerate_n, &framerate_d); + + to_caps = gst_caps_new_simple ("video/x-raw-rgb", + "bpp", G_TYPE_INT, 24, + "depth", G_TYPE_INT, 24, + "framerate", GST_TYPE_FRACTION, + framerate_n, framerate_d, + "pixel-aspect-ratio", + GST_TYPE_FRACTION, 1, 1, + "endianness", + G_TYPE_INT, G_BIG_ENDIAN, + "red_mask", G_TYPE_INT, + 0xff0000, + "green_mask", + G_TYPE_INT, 0x00ff00, + "blue_mask", + G_TYPE_INT, 0x0000ff, + NULL); + + sgd = g_new0(SaveGraphicData, 1); + sgd->worker = worker; + sgd->metadata_key = g_strdup(metadata_key); + + g_debug("pixbuf: using bvw to convert image format"); + bvw_frame_conv_convert (buffer, to_caps, + _emit_gst_buffer_as_graphic_file_cb, + sgd); + } else { + GdkPixbuf *pixbuf = NULL; + loader = gdk_pixbuf_loader_new_with_mime_type(mime, &error); + g_signal_connect (G_OBJECT (loader), "size-prepared", + (GCallback)_pixbuf_size_prepared_cb, NULL); + + if (loader == NULL) { + g_warning ("%s\n", error->message); + g_error_free (error); + } else { + if (!gdk_pixbuf_loader_write (loader, + GST_BUFFER_DATA(buffer), + GST_BUFFER_SIZE(buffer), + &error)) { + g_warning ("%s\n", error->message); + g_error_free (error); + + gdk_pixbuf_loader_close (loader, NULL); + } else { + pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + + if (!gdk_pixbuf_loader_close (loader, &error)) { + g_warning ("%s\n", error->message); + g_error_free (error); + + g_object_unref(pixbuf); + } else { + SaveGraphicData *sgd; + + sgd = g_new0(SaveGraphicData, 1); + + sgd->worker = worker; + sgd->metadata_key = + g_strdup(metadata_key); + sgd->pixbuf = pixbuf; + + _emit_gst_buffer_as_graphic_file_cb( + NULL, sgd); + } + } + } + } +} +#endif + +static gboolean _go_to_gst_ready(gpointer user_data) +{ + MafwGstRendererWorker *worker = user_data; + + g_return_val_if_fail(worker->state == GST_STATE_PAUSED || + worker->prerolling, FALSE); + + worker->seek_position = + mafw_gst_renderer_worker_get_position(worker); + + g_debug("going to GST_STATE_READY"); + gst_element_set_state(worker->pipeline, GST_STATE_READY); + worker->in_ready = TRUE; + worker->ready_timeout = 0; + + return FALSE; +} + +static void _add_ready_timeout(MafwGstRendererWorker *worker) +{ + if (worker->media.seekable) { + if (!worker->ready_timeout) + { + g_debug("Adding timeout to go to GST_STATE_READY"); + worker->ready_timeout = + g_timeout_add_seconds( + MAFW_GST_RENDERER_WORKER_SECONDS_READY, + _go_to_gst_ready, + worker); + } + } else { + g_debug("Not adding timeout to go to GST_STATE_READY as media " + "is not seekable"); + worker->ready_timeout = 0; + } +} + +static void _remove_ready_timeout(MafwGstRendererWorker *worker) +{ + g_debug("removing timeout for READY"); + if (worker->ready_timeout != 0) { + g_source_remove(worker->ready_timeout); + worker->ready_timeout = 0; + } + worker->in_ready = FALSE; +} + +static gboolean _emit_video_info(MafwGstRendererWorker *worker) +{ + mafw_renderer_emit_metadata_int(worker->owner, + MAFW_METADATA_KEY_RES_X, + worker->media.video_width); + mafw_renderer_emit_metadata_int(worker->owner, + MAFW_METADATA_KEY_RES_Y, + worker->media.video_height); + mafw_renderer_emit_metadata_double(worker->owner, + MAFW_METADATA_KEY_VIDEO_FRAMERATE, + worker->media.fps); + return FALSE; +} + +/* + * Checks if the video details are supported. It also extracts other useful + * information (such as PAR and framerate) from the caps, if available. NOTE: + * this will be called from a different thread than glib's mainloop (when + * invoked via _stream_info_cb); don't call MafwGstRenderer directly. + * + * Returns: TRUE if video details are acceptable. + */ +static gboolean _handle_video_info(MafwGstRendererWorker *worker, + const GstStructure *structure) +{ + gint width, height; + gdouble fps; + + width = height = 0; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + g_debug("video size: %d x %d", width, height); + if (gst_structure_has_field(structure, "pixel-aspect-ratio")) + { + gst_structure_get_fraction(structure, "pixel-aspect-ratio", + &worker->media.par_n, + &worker->media.par_d); + g_debug("video PAR: %d:%d", worker->media.par_n, + worker->media.par_d); + width = width * worker->media.par_n / worker->media.par_d; + } + + fps = 1.0; + if (gst_structure_has_field(structure, "framerate")) + { + gint fps_n, fps_d; + + gst_structure_get_fraction(structure, "framerate", + &fps_n, &fps_d); + if (fps_d > 0) + fps = (gdouble)fps_n / (gdouble)fps_d; + g_debug("video fps: %f", fps); + } + + worker->media.video_width = width; + worker->media.video_height = height; + worker->media.fps = fps; + + /* Add the info to the current metadata. */ + gint *p_width = g_new0(gint, 1); + gint *p_height = g_new0(gint, 1); + gdouble *p_fps = g_new0(gdouble, 1); + + *p_width = width;* p_height = height; *p_fps = fps; + + _current_metadata_add(worker, MAFW_METADATA_KEY_RES_X, G_TYPE_INT, + (const gpointer) p_width); + _current_metadata_add(worker, MAFW_METADATA_KEY_RES_Y, G_TYPE_INT, + (const gpointer) p_height); + _current_metadata_add(worker, MAFW_METADATA_KEY_VIDEO_FRAMERATE, + G_TYPE_DOUBLE, + (const gpointer) p_fps); + + g_free(p_width); g_free(p_height); g_free(p_fps); + + /* Emit the metadata.*/ + g_idle_add((GSourceFunc)_emit_video_info, worker); + + return TRUE; +} + +static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj) +{ + GParamSpec *pspec; + GEnumValue *val; + gint type; + + g_object_get(obj, "type", &type, NULL); + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type"); + val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type); + if (!val) + return; + if (!g_ascii_strcasecmp(val->value_nick, "video") || + !g_ascii_strcasecmp(val->value_name, "video")) + { + GstCaps *vcaps; + GstObject *object; + + object = NULL; + g_object_get(obj, "object", &object, NULL); + vcaps = NULL; + if (object) { + vcaps = gst_pad_get_caps(GST_PAD_CAST(object)); + } else { + g_object_get(obj, "caps", &vcaps, NULL); + gst_caps_ref(vcaps); + } + if (vcaps) { + if (gst_caps_is_fixed(vcaps)) + { + _handle_video_info( + worker, + gst_caps_get_structure(vcaps, 0)); + } + gst_caps_unref(vcaps); + } + } +} + +/* It always returns FALSE, because it is used as an idle callback as well. */ +static gboolean _parse_stream_info(MafwGstRendererWorker *worker) +{ + GList *stream_info, *s; + + stream_info = NULL; + if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline), + "stream-info")) + { + g_object_get(worker->pipeline, + "stream-info", &stream_info, NULL); + } + for (s = stream_info; s; s = g_list_next(s)) + _parse_stream_info_item(worker, G_OBJECT(s->data)); + return FALSE; +} + +static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker) +{ + /* Set sink to render on the provided XID if we have do have + a XID a valid video sink and we are rendeing video content */ + if (worker->xid && + worker->vsink && + worker->media.has_visual_content) + { + g_debug ("Setting overlay, window id: %x", + (gint) worker->xid); + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink), + worker->xid); + /* Ask the gst to redraw the frame if we are paused */ + /* TODO: in MTG this works only in non-fs -> fs way. */ + if (worker->state == GST_STATE_PAUSED) + { + gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink)); + } + } else { + g_debug("Not setting overlay for window id: %x", + (gint) worker->xid); + } +} + +/* + * GstBus synchronous message handler. NOTE that this handler is NOT invoked + * from the glib thread, so be careful what you do here. + */ +static GstBusSyncReply _sync_bus_handler(GstBus *bus, GstMessage *msg, + MafwGstRendererWorker *worker) +{ + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(msg->structure, "prepare-xwindow-id")) + { + g_debug("got prepare-xwindow-id"); + worker->media.has_visual_content = TRUE; + /* The user has to preset the XID, we don't create windows by + * ourselves. */ + if (!worker->xid) { + /* We must post an error message to the bus that will + * be picked up by _async_bus_handler. Calling the + * notification function directly from here (different + * thread) is not healthy. */ + g_warning("No video window set!"); + _post_error(worker, + g_error_new_literal( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_PLAYBACK, + "No video window XID set")); + return GST_BUS_DROP; + } else { + g_debug ("Video window to use is: %x", + (gint) worker->xid); + } + + /* Instruct vsink to use the client-provided window */ + mafw_gst_renderer_worker_apply_xid(worker); + + /* Handle colorkey and autopaint */ + mafw_gst_renderer_worker_set_autopaint( + worker, + worker->autopaint); + g_object_get(worker->vsink, + "colorkey", &worker->colorkey, NULL); + /* Defer the signal emission to the thread running the + * mainloop. */ + if (worker->colorkey != -1) { + gst_bus_post(worker->bus, + gst_message_new_application( + GST_OBJECT(worker->vsink), + gst_structure_empty_new("ckey"))); + } + return GST_BUS_DROP; + } + return GST_BUS_PASS; +} + +static void _free_taglist_item(GstMessage *msg, gpointer data) +{ + gst_message_unref(msg); +} + +static void _free_taglist(MafwGstRendererWorker *worker) +{ + if (worker->tag_list != NULL) + { + g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item, + NULL); + g_ptr_array_free(worker->tag_list, TRUE); + worker->tag_list = NULL; + } +} + +static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2) +{ + gint64 duration1_seconds, duration2_seconds; + + duration1_seconds = NSECONDS_TO_SECONDS(duration1); + duration2_seconds = NSECONDS_TO_SECONDS(duration2); + + return duration1_seconds == duration2_seconds; +} + +static void _check_duration(MafwGstRendererWorker *worker, gint64 value) +{ + MafwGstRenderer *renderer = worker->owner; + gboolean right_query = TRUE; + + if (value == -1) { + GstFormat format = GST_FORMAT_TIME; + right_query = + gst_element_query_duration(worker->pipeline, &format, + &value); + } + + if (right_query && value > 0) { + gint duration_seconds = NSECONDS_TO_SECONDS(value); + + if (!_seconds_duration_equal(worker->media.length_nanos, + value)) { + gint64 *duration = g_new0(gint64, 1); + *duration = duration_seconds; + + /* Add the duration to the current metadata. */ + _current_metadata_add(worker, + MAFW_METADATA_KEY_DURATION, + G_TYPE_INT64, + (const gpointer) duration); + + /* Emit the duration. */ + mafw_renderer_emit_metadata_int64( + worker->owner, MAFW_METADATA_KEY_DURATION, + *duration); + g_free(duration); + } + + /* We compare this duration we just got with the + * source one and update it in the source if needed */ + if (duration_seconds != renderer->media->duration) { + mafw_gst_renderer_update_source_duration( + renderer, + duration_seconds); + } + } + + worker->media.length_nanos = value; + g_debug("media duration: %lld", worker->media.length_nanos); +} + +static void _check_seekability(MafwGstRendererWorker *worker) +{ + MafwGstRenderer *renderer = worker->owner; + SeekabilityType seekable = SEEKABILITY_NO_SEEKABLE; + + if (worker->media.length_nanos != -1) + { + g_debug("source seekability %d", renderer->media->seekability); + + if (renderer->media->seekability != SEEKABILITY_NO_SEEKABLE) { + g_debug("Quering GStreamer for seekability"); + GstQuery *seek_query; + GstFormat format = GST_FORMAT_TIME; + /* Query the seekability of the stream */ + seek_query = gst_query_new_seeking(format); + if (gst_element_query(worker->pipeline, seek_query)) { + gboolean renderer_seekable = FALSE; + gst_query_parse_seeking(seek_query, NULL, + &renderer_seekable, + NULL, NULL); + g_debug("GStreamer seekability %d", + renderer_seekable); + seekable = renderer_seekable ? + SEEKABILITY_SEEKABLE : + SEEKABILITY_NO_SEEKABLE; + } + gst_query_unref(seek_query); + } + } + + if (worker->media.seekable != seekable) { + gboolean *is_seekable = g_new0(gboolean, 1); + *is_seekable = (seekable == SEEKABILITY_SEEKABLE) ? TRUE : FALSE; + + /* Add the seekability to the current metadata. */ + _current_metadata_add(worker, MAFW_METADATA_KEY_IS_SEEKABLE, + G_TYPE_BOOLEAN, (const gpointer) is_seekable); + + /* Emit. */ + mafw_renderer_emit_metadata_boolean( + worker->owner, MAFW_METADATA_KEY_IS_SEEKABLE, + *is_seekable); + + g_free(is_seekable); + } + + g_debug("media seekable: %d", seekable); + worker->media.seekable = seekable; +} + +static gboolean _query_duration_and_seekability_timeout(gpointer data) +{ + MafwGstRendererWorker *worker = data; + + _check_duration(worker, -1); + _check_seekability(worker); + + worker->duration_seek_timeout = 0; + + return FALSE; +} + +/* + * Called when the pipeline transitions into PAUSED state. It extracts more + * information from Gst. + */ +static void _finalize_startup(MafwGstRendererWorker *worker) +{ + /* Check video caps */ + if (worker->media.has_visual_content) { + GstPad *pad = GST_BASE_SINK_PAD(worker->vsink); + GstCaps *caps = GST_PAD_CAPS(pad); + if (caps && gst_caps_is_fixed(caps)) { + GstStructure *structure; + structure = gst_caps_get_structure(caps, 0); + if (!_handle_video_info(worker, structure)) + return; + } + } + + /* Something might have gone wrong at this point already. */ + if (worker->is_error) { + g_debug("Error occured during preroll"); + return; + } + + /* Streaminfo might reveal the media to be unsupported. Therefore we + * need to check the error again. */ + _parse_stream_info(worker); + if (worker->is_error) { + g_debug("Error occured. Leaving"); + return; + } + + /* Check duration and seekability */ + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + worker->duration_seek_timeout = 0; + } + _check_duration(worker, -1); + _check_seekability(worker); +} + +static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker) +{ + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + } + worker->duration_seek_timeout = g_timeout_add_seconds( + MAFW_GST_RENDERER_WORKER_SECONDS_DURATION_AND_SEEKABILITY, + _query_duration_and_seekability_timeout, + worker); +} + +static void _do_pause_postprocessing(MafwGstRendererWorker *worker) +{ + if (worker->notify_pause_handler) { + worker->notify_pause_handler(worker, worker->owner); + } + +#ifdef HAVE_GDKPIXBUF + if (worker->media.has_visual_content && + worker->current_frame_on_pause) { + GstBuffer *buffer = NULL; + + g_object_get(worker->pipeline, "frame", &buffer, NULL); + + if (buffer != NULL) { + _emit_gst_buffer_as_graphic_file( + worker, buffer, + MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI); + } + } +#endif + + _add_ready_timeout(worker); +} + +static void _report_playing_state(MafwGstRendererWorker * worker) +{ + if (worker->report_statechanges) { + switch (worker->mode) { + case WORKER_MODE_SINGLE_PLAY: + /* Notify play if we are playing in + * single mode */ + if (worker->notify_play_handler) + worker->notify_play_handler( + worker, + worker->owner); + break; + case WORKER_MODE_PLAYLIST: + case WORKER_MODE_REDUNDANT: + /* Only notify play when the "playlist" + playback starts, don't notify play for each + individual element of the playlist. */ + if (worker->pl.notify_play_pending) { + if (worker->notify_play_handler) + worker->notify_play_handler( + worker, + worker->owner); + worker->pl.notify_play_pending = FALSE; + } + break; + default: break; + } + } +} + +static void _handle_state_changed(GstMessage *msg, MafwGstRendererWorker *worker) +{ + GstState newstate, oldstate; + GstStateChange statetrans; + MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner; + + gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL); + statetrans = GST_STATE_TRANSITION(oldstate, newstate); + g_debug ("State changed: %d: %d -> %d", worker->state, oldstate, newstate); + + /* If the state is the same we do nothing, otherwise, we keep + * it */ + if (worker->state == newstate) { + return; + } else { + worker->state = newstate; + } + + if (statetrans == GST_STATE_CHANGE_READY_TO_PAUSED && + worker->in_ready) { + /* Woken up from READY, resume stream position and playback */ + g_debug("State changed to pause after ready"); + if (worker->seek_position > 0) { + _check_seekability(worker); + if (worker->media.seekable) { + g_debug("performing a seek"); + _do_seek(worker, GST_SEEK_TYPE_SET, + worker->seek_position, NULL); + } else { + g_critical("media is not seekable (and should)"); + } + } + + /* If playing a stream wait for buffering to finish before + starting to play */ + if (!worker->is_stream || worker->is_live) { + _do_play(worker); + } + return; + } + + /* While buffering, we have to wait in PAUSED + until we reach 100% before doing anything */ + if (worker->buffering) { + if (statetrans == GST_STATE_CHANGE_PAUSED_TO_PLAYING) { + /* Mmm... probably the client issued a seek on the + * stream and then a play/resume command right away, + * so the stream got into PLAYING state while + * buffering. When the next buffering signal arrives, + * the stream will be PAUSED silently and resumed when + * buffering is done (silently too), so let's signal + * the state change to PLAYING here. */ + _report_playing_state(worker); + } + return; + } + + switch (statetrans) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (worker->prerolling && worker->report_statechanges) { + /* PAUSED after pipeline has been + * constructed. We check caps, seek and + * duration and if staying in pause is needed, + * we perform operations for pausing, such as + * current frame on pause and signalling state + * change and adding the timeout to go to ready */ + g_debug ("Prerolling done, finalizaing startup"); + _finalize_startup(worker); + _do_play(worker); + renderer->play_failed_count = 0; + + if (worker->stay_paused) { + _do_pause_postprocessing(worker); + } + worker->prerolling = FALSE; + } + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* When pausing we do the stuff, like signalling + * state, current frame on pause and timeout to go to + * ready */ + if (worker->report_statechanges) { + _do_pause_postprocessing(worker); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* if seek was called, at this point it is really ended */ + worker->seek_position = -1; + worker->eos = FALSE; + + /* Signal state change if needed */ + _report_playing_state(worker); + + /* Prevent blanking if we are playing video */ + if (worker->media.has_visual_content) { + blanking_prohibit(); + } + /* Remove the ready timeout if we are playing [again] */ + _remove_ready_timeout(worker); + /* If mode is redundant we are trying to play one of several + * candidates, so when we get a successful playback, we notify + * the real URI that we are playing */ + if (worker->mode == WORKER_MODE_REDUNDANT) { + mafw_renderer_emit_metadata_string( + worker->owner, + MAFW_METADATA_KEY_URI, + worker->media.location); + } + + /* Emit metadata. We wait until we reach the playing + state because this speeds up playback start time */ + _emit_metadatas(worker); + /* Query duration and seekability. Useful for vbr + * clips or streams. */ + _add_duration_seek_query_timeout(worker); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* If we went to READY, we free the taglist and + * deassign the timout it */ + if (worker->in_ready) { + g_debug("changed to GST_STATE_READY"); + _free_taglist(worker); + } + break; + default: + break; + } +} + +static void _handle_duration(MafwGstRendererWorker *worker, GstMessage *msg) +{ + GstFormat fmt; + gint64 duration; + + gst_message_parse_duration(msg, &fmt, &duration); + + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + worker->duration_seek_timeout = 0; + } + + _check_duration(worker, + duration != GST_CLOCK_TIME_NONE ? duration : -1); + _check_seekability(worker); +} + +#ifdef HAVE_GDKPIXBUF +static void _emit_renderer_art(MafwGstRendererWorker *worker, + const GstTagList *list) +{ + GstBuffer *buffer = NULL; + const GValue *value = NULL; + + g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0); + + value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0); + + g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER)); + + buffer = g_value_peek_pointer(value); + + g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer)); + + _emit_gst_buffer_as_graphic_file(worker, buffer, + MAFW_METADATA_KEY_RENDERER_ART_URI); +} +#endif + + + +static void _current_metadata_add(MafwGstRendererWorker *worker, + const gchar *key, GType type, + const gpointer value) +{ + g_return_if_fail(value != NULL); + + if (!worker->current_metadata) + worker->current_metadata = mafw_metadata_new(); + + mafw_metadata_add_something(worker->current_metadata, key, type, 1, value); +} + +static GHashTable* _build_tagmap(void) +{ + GHashTable *hash_table = NULL; + + hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + g_free); + + g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE), + g_strdup(MAFW_METADATA_KEY_TITLE)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST), + g_strdup(MAFW_METADATA_KEY_ARTIST)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC), + g_strdup(MAFW_METADATA_KEY_AUDIO_CODEC)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC), + g_strdup(MAFW_METADATA_KEY_VIDEO_CODEC)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE), + g_strdup(MAFW_METADATA_KEY_BITRATE)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE), + g_strdup(MAFW_METADATA_KEY_ENCODING)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM), + g_strdup(MAFW_METADATA_KEY_ALBUM)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE), + g_strdup(MAFW_METADATA_KEY_GENRE)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER), + g_strdup(MAFW_METADATA_KEY_TRACK)); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION), + g_strdup(MAFW_METADATA_KEY_ORGANIZATION)); +#ifdef HAVE_GDKPIXBUF + g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE), + g_strdup(MAFW_METADATA_KEY_RENDERER_ART_URI)); +#endif + + return hash_table; +} + +/* + * Emits metadata-changed signals for gst tags. + */ +static void _emit_tag(const GstTagList *list, const gchar *tag, + MafwGstRendererWorker *worker) +{ + /* Mapping between Gst <-> MAFW metadata tags + * NOTE: This assumes that GTypes matches between GST and MAFW. */ + static GHashTable *tagmap = NULL; + gint i, count; + const gchar *mafwtag; + GType type; + GValueArray *values; + + if (tagmap == NULL) { + tagmap = _build_tagmap(); + } + + g_debug("tag: '%s' (type: %s)", tag, + g_type_name(gst_tag_get_type(tag))); + /* Is there a mapping for this tag? */ + mafwtag = g_hash_table_lookup(tagmap, tag); + if (!mafwtag) + return; + +#ifdef HAVE_GDKPIXBUF + if (strcmp (mafwtag, MAFW_METADATA_KEY_RENDERER_ART_URI) == 0) { + _emit_renderer_art(worker, list); + return; + } +#endif + + /* Build a value array of this tag. We need to make sure that strings + * are UTF-8. GstTagList API says that the value is always UTF8, but it + * looks like the ID3 demuxer still might sometimes produce non-UTF-8 + * strings. */ + count = gst_tag_list_get_tag_size(list, tag); + type = gst_tag_get_type(tag); + values = g_value_array_new(count); + for (i = 0; i < count; ++i) { + GValue *v = (GValue *) + gst_tag_list_get_value_index(list, tag, i); + if (type == G_TYPE_STRING) { + gchar *orig, *utf8; + + gst_tag_list_get_string_index(list, tag, i, &orig); + if (convert_utf8(orig, &utf8)) { + GValue utf8gval = {0}; + + g_value_init(&utf8gval, G_TYPE_STRING); + g_value_take_string(&utf8gval, utf8); + _current_metadata_add(worker, mafwtag, G_TYPE_VALUE, + (const gpointer) &utf8gval); + g_value_array_append(values, &utf8gval); + g_value_unset(&utf8gval); + } + g_free(orig); + } else if (type == G_TYPE_UINT) { + GValue intgval = {0}; + + g_value_init(&intgval, G_TYPE_INT); + g_value_transform(v, &intgval); + _current_metadata_add(worker, mafwtag, G_TYPE_VALUE, + (const gpointer) &intgval); + g_value_array_append(values, &intgval); + g_value_unset(&intgval); + } else { + _current_metadata_add(worker, mafwtag, G_TYPE_VALUE, + (const gpointer) v); + g_value_array_append(values, v); + } + } + + /* Emit the metadata. */ + g_signal_emit_by_name(worker->owner, "metadata-changed", mafwtag, + values); + + g_value_array_free(values); +} + +/** + * Collect tag-messages, parse it later, when playing is ongoing + */ +static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg) +{ + /* Do not emit metadata until we get to PLAYING state to speed up + playback start */ + if (worker->tag_list == NULL) + worker->tag_list = g_ptr_array_new(); + g_ptr_array_add(worker->tag_list, gst_message_ref(msg)); + + /* Some tags come in playing state, so in this case we have + to emit them right away (example: radio stations) */ + if (worker->state == GST_STATE_PLAYING) { + _emit_metadatas(worker); + } +} + +/** + * Parses the list of tag-messages + */ +static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker) +{ + GstTagList *new_tags; + + gst_message_parse_tag(msg, &new_tags); + gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker); + gst_tag_list_free(new_tags); + gst_message_unref(msg); +} + +/** + * Parses the collected tag messages, and emits the metadatas + */ +static void _emit_metadatas(MafwGstRendererWorker *worker) +{ + if (worker->tag_list != NULL) + { + g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg, + worker); + g_ptr_array_free(worker->tag_list, TRUE); + worker->tag_list = NULL; + } +} + +static void _reset_volume_and_mute_to_pipeline(MafwGstRendererWorker *worker) +{ +#ifdef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME + g_debug("resetting volume and mute to pipeline"); + + if (worker->pipeline != NULL) { + g_object_set( + G_OBJECT(worker->pipeline), "volume", + mafw_gst_renderer_worker_volume_get(worker->wvolume), + "mute", + mafw_gst_renderer_worker_volume_is_muted(worker->wvolume), + NULL); + } +#endif +} + +static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg) +{ + gint percent; + MafwGstRenderer *renderer = (MafwGstRenderer*)worker->owner; + + gst_message_parse_buffering(msg, &percent); + g_debug("buffering: %d", percent); + + /* No state management needed for live pipelines */ + if (!worker->is_live) { + worker->buffering = TRUE; + if (worker->state == GST_STATE_PLAYING) { + g_debug("setting pipeline to PAUSED not to wolf the " + "buffer down"); + worker->report_statechanges = FALSE; + /* We can't call _pause() here, since it sets + * the "report_statechanges" to TRUE. We don't + * want that, application doesn't need to know + * that internally the state changed to + * PAUSED. */ + if (gst_element_set_state(worker->pipeline, + GST_STATE_PAUSED) == + GST_STATE_CHANGE_ASYNC) + { + /* XXX this blocks at most 2 seconds. */ + gst_element_get_state(worker->pipeline, NULL, + NULL, + 2 * GST_SECOND); + } + } + + if (percent >= 100) { + /* On buffering we go to PAUSED, so here we move back to + PLAYING */ + worker->buffering = FALSE; + if (worker->state == GST_STATE_PAUSED) { + /* If buffering more than once, do this only the + first time we are done with buffering */ + if (worker->prerolling) { + g_debug("buffering concluded during " + "prerolling"); + _finalize_startup(worker); + _do_play(worker); + renderer->play_failed_count = 0; + /* Send the paused notification */ + if (worker->stay_paused && + worker->notify_pause_handler) { + worker->notify_pause_handler( + worker, + worker->owner); + } + worker->prerolling = FALSE; + } else if (worker->in_ready) { + /* If we had been woken up from READY + and we have finish our buffering, + check if we have to play or stay + paused and if we have to play, + signal the state change. */ + g_debug("buffering concluded, " + "continuing playing"); + _do_play(worker); + } else if (!worker->stay_paused) { + /* This means, that we were playing but + ran out of buffer, so we silently + paused waited for buffering to + finish and now we continue silently + (silently meaning we do not expose + state changes) */ + g_debug("buffering concluded, setting " + "pipeline to PLAYING again"); + _reset_volume_and_mute_to_pipeline( + worker); + if (gst_element_set_state( + worker->pipeline, + GST_STATE_PLAYING) == + GST_STATE_CHANGE_ASYNC) + { + /* XXX this blocks at most 2 seconds. */ + gst_element_get_state( + worker->pipeline, NULL, NULL, + 2 * GST_SECOND); + } + } + } else if (worker->state == GST_STATE_PLAYING) { + g_debug("buffering concluded, signalling " + "state change"); + /* In this case we got a PLAY command while + buffering, likely because it was issued + before we got the first buffering signal. + The UI should not do this, but if it does, + we have to signal that we have executed + the state change, since in + _handle_state_changed we do not do anything + if we are buffering */ + if (worker->report_statechanges && + worker->notify_play_handler) { + worker->notify_play_handler( + worker, + worker->owner); + } + _add_duration_seek_query_timeout(worker); + } + } + } + + /* Send buffer percentage */ + if (worker->notify_buffer_status_handler) + worker->notify_buffer_status_handler(worker, worker->owner, + percent); +} + +static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg) +{ + /* Only HelixBin sends "resolution" messages. */ + if (gst_structure_has_name(msg->structure, "resolution") && + _handle_video_info(worker, msg->structure)) + { + worker->media.has_visual_content = TRUE; + } +} + +static void _reset_pl_info(MafwGstRendererWorker *worker) +{ + if (worker->pl.items) { + g_slist_foreach(worker->pl.items, (GFunc) g_free, NULL); + g_slist_free(worker->pl.items); + worker->pl.items = NULL; + } + + worker->pl.current = 0; + worker->pl.notify_play_pending = TRUE; +} + +static GError * _get_specific_missing_plugin_error(GstMessage *msg) +{ + const GstStructure *gst_struct; + const gchar *type; + + GError *error; + gchar *desc; + + desc = gst_missing_plugin_message_get_description(msg); + + gst_struct = gst_message_get_structure(msg); + type = gst_structure_get_string(gst_struct, "type"); + + if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) || + (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) { + + /* Missing codec error. */ + const GValue *val; + const GstCaps *caps; + GstStructure *caps_struct; + const gchar *mime; + + val = gst_structure_get_value(gst_struct, "detail"); + caps = gst_value_get_caps(val); + caps_struct = gst_caps_get_structure(caps, 0); + mime = gst_structure_get_name(caps_struct); + + if (g_strrstr(mime, "video")) { + error = g_error_new_literal( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_VIDEO_CODEC_NOT_FOUND, + desc); + } else if (g_strrstr(mime, "audio")) { + error = g_error_new_literal( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_AUDIO_CODEC_NOT_FOUND, + desc); + } else { + error = g_error_new_literal( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_CODEC_NOT_FOUND, + desc); + } + } else { + /* Unsupported type error. */ + error = g_error_new( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE, + "missing plugin: %s", desc); + } + + g_free(desc); + + return error; +} + +/* + * Asynchronous message handler. It gets removed from if it returns FALSE. + */ +static gboolean _async_bus_handler(GstBus *bus, GstMessage *msg, + MafwGstRendererWorker *worker) +{ + /* No need to handle message if error has already occured. */ + if (worker->is_error) + return TRUE; + + /* Handle missing-plugin (element) messages separately, relaying more + * details. */ + if (gst_is_missing_plugin_message(msg)) { + GError *err = _get_specific_missing_plugin_error(msg); + /* FIXME?: for some reason, calling the error handler directly + * (_send_error) causes problems. On the other hand, turning + * the error into a new GstMessage and letting the next + * iteration handle it seems to work. */ + _post_error(worker, err); + return TRUE; + } + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + if (!worker->is_error) { + gchar *debug; + GError *err; + + debug = NULL; + gst_message_parse_error(msg, &err, &debug); + g_debug("gst error: domain = %d, code = %d, " + "message = '%s', debug = '%s'", + err->domain, err->code, err->message, debug); + if (debug) + g_free(debug); + + /* If we are in playlist/radio mode, we silently + ignore the error and continue with the next + item until we end the playlist. If no + playable elements we raise the error and + after finishing we go to normal mode */ + + if (worker->mode == WORKER_MODE_PLAYLIST || + worker->mode == WORKER_MODE_REDUNDANT) { + if (worker->pl.current < + (g_slist_length(worker->pl.items) - 1)) { + /* If the error is "no space left" + notify, otherwise try to play the + next item */ + if (err->code == + GST_RESOURCE_ERROR_NO_SPACE_LEFT) { + _send_error(worker, err); + + } else { + _play_pl_next(worker); + } + } else { + /* Playlist EOS. We cannot try another + * URI, so we have to go back to normal + * mode and signal the error (done + * below) */ + worker->mode = WORKER_MODE_SINGLE_PLAY; + _reset_pl_info(worker); + } + } + + if (worker->mode == WORKER_MODE_SINGLE_PLAY) { + _send_error(worker, err); + } + } + break; + case GST_MESSAGE_EOS: + if (!worker->is_error) { + worker->eos = TRUE; + + if (worker->mode == WORKER_MODE_PLAYLIST) { + if (worker->pl.current < + (g_slist_length(worker->pl.items) - 1)) { + /* If the playlist EOS is not reached + continue playing */ + _play_pl_next(worker); + } else { + /* Playlist EOS, go back to normal + mode */ + worker->mode = WORKER_MODE_SINGLE_PLAY; + _reset_pl_info(worker); + } + } + + if (worker->mode == WORKER_MODE_SINGLE_PLAY || + worker->mode == WORKER_MODE_REDUNDANT) { + if (worker->notify_eos_handler) + worker->notify_eos_handler( + worker, + worker->owner); + + /* We can remove the message handlers now, we + are not interested in bus messages + anymore. */ + if (worker->bus) { + gst_bus_set_sync_handler(worker->bus, + NULL, + NULL); + } + if (worker->async_bus_id) { + g_source_remove(worker->async_bus_id); + worker->async_bus_id = 0; + } + + if (worker->mode == WORKER_MODE_REDUNDANT) { + /* Go to normal mode */ + worker->mode = WORKER_MODE_SINGLE_PLAY; + _reset_pl_info(worker); + } + } + } + break; + case GST_MESSAGE_TAG: + _handle_tag(worker, msg); + break; + case GST_MESSAGE_BUFFERING: + _handle_buffering(worker, msg); + break; + case GST_MESSAGE_DURATION: + _handle_duration(worker, msg); + break; + case GST_MESSAGE_ELEMENT: + _handle_element_msg(worker, msg); + break; + case GST_MESSAGE_STATE_CHANGED: + if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline) + _handle_state_changed(msg, worker); + break; + case GST_MESSAGE_APPLICATION: + if (gst_structure_has_name(gst_message_get_structure(msg), + "ckey")) + { + GValue v = {0}; + g_value_init(&v, G_TYPE_INT); + g_value_set_int(&v, worker->colorkey); + mafw_extension_emit_property_changed( + MAFW_EXTENSION(worker->owner), + MAFW_PROPERTY_RENDERER_COLORKEY, + &v); + } + default: break; + } + return TRUE; +} + +/* NOTE this function will possibly be called from a different thread than the + * glib main thread. */ +static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused, + MafwGstRendererWorker *worker) +{ + g_debug("stream-info changed"); + _parse_stream_info(worker); +} + +static void _volume_cb(MafwGstRendererWorkerVolume *wvolume, gdouble volume, + gpointer data) +{ + MafwGstRendererWorker *worker = data; + GValue value = {0, }; + + _reset_volume_and_mute_to_pipeline(worker); + + g_value_init(&value, G_TYPE_UINT); + g_value_set_uint(&value, (guint) (volume * 100.0)); + mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner), + MAFW_PROPERTY_RENDERER_VOLUME, + &value); +} + +static void _mute_cb(MafwGstRendererWorkerVolume *wvolume, gboolean mute, + gpointer data) +{ + MafwGstRendererWorker *worker = data; + GValue value = {0, }; + + _reset_volume_and_mute_to_pipeline(worker); + + g_value_init(&value, G_TYPE_BOOLEAN); + g_value_set_boolean(&value, mute); + mafw_extension_emit_property_changed(MAFW_EXTENSION(worker->owner), + MAFW_PROPERTY_RENDERER_MUTE, + &value); +} + +/* TODO: I think it's not enought to act on error, we need to handle + * DestroyNotify on the given window ourselves, because for example helixbin + * does it and silently stops the decoder thread. But it doesn't notify + * us... */ +static int xerror(Display *dpy, XErrorEvent *xev) +{ + MafwGstRendererWorker *worker; + + if (Global_worker == NULL) { + return -1; + } else { + worker = Global_worker; + } + + /* Swallow BadWindow and stop pipeline when the error is about the + * currently set xid. */ + if (worker->xid && + xev->resourceid == worker->xid && + xev->error_code == BadWindow) + { + g_warning("BadWindow received for current xid (%x).", + (gint)xev->resourceid); + worker->xid = 0; + /* We must post a message to the bus, because this function is + * invoked from a different thread (xvimagerenderer's queue). */ + _post_error(worker, g_error_new_literal( + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_PLAYBACK, + "Video window gone")); + } + return 0; +} + +/* + * Resets the media information. + */ +static void _reset_media_info(MafwGstRendererWorker *worker) +{ + if (worker->media.location) { + g_free(worker->media.location); + worker->media.location = NULL; + } + worker->media.length_nanos = -1; + worker->media.has_visual_content = FALSE; + worker->media.seekable = SEEKABILITY_UNKNOWN; + worker->media.video_width = 0; + worker->media.video_height = 0; + worker->media.fps = 0.0; +} + +static void _set_volume_and_mute(MafwGstRendererWorker *worker, gdouble vol, + gboolean mute) +{ + g_return_if_fail(worker->wvolume != NULL); + + mafw_gst_renderer_worker_volume_set(worker->wvolume, vol, mute); +} + +static void _set_volume(MafwGstRendererWorker *worker, gdouble new_vol) +{ + g_return_if_fail(worker->wvolume != NULL); + + _set_volume_and_mute( + worker, new_vol, + mafw_gst_renderer_worker_volume_is_muted(worker->wvolume)); +} + +static void _set_mute(MafwGstRendererWorker *worker, gboolean mute) +{ + g_return_if_fail(worker->wvolume != NULL); + + _set_volume_and_mute( + worker, mafw_gst_renderer_worker_volume_get(worker->wvolume), + mute); +} + +/* + * Start to play the media + */ +static void _start_play(MafwGstRendererWorker *worker) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) worker->owner; + GstStateChangeReturn state_change_info; + + g_assert(worker->pipeline); + g_object_set(G_OBJECT(worker->pipeline), + "uri", worker->media.location, NULL); + + g_debug("URI: %s", worker->media.location); + g_debug("setting pipeline to PAUSED"); + + worker->report_statechanges = TRUE; + state_change_info = gst_element_set_state(worker->pipeline, + GST_STATE_PAUSED); + if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) { + /* FIXME: for live sources we may have to handle + buffering and prerolling differently */ + g_debug ("Source is live!"); + worker->is_live = TRUE; + } + worker->prerolling = TRUE; + + worker->is_stream = uri_is_stream(worker->media.location); + + if (renderer->update_playcount_id > 0) { + g_source_remove(renderer->update_playcount_id); + renderer->update_playcount_id = 0; + } + +} + +/* + * Constructs gst pipeline + * + * FIXME: Could the same pipeline be used for playing all media instead of + * constantly deleting and reconstructing it again? + */ +static void _construct_pipeline(MafwGstRendererWorker *worker) +{ + g_debug("constructing pipeline"); + g_assert(worker != NULL); + + /* Return if we have already one */ + if (worker->pipeline) + return; + + _free_taglist(worker); + + g_debug("Creating a new instance of playbin2"); + worker->pipeline = gst_element_factory_make("playbin2", + "playbin"); + if (worker->pipeline == NULL) + { + /* Let's try with playbin */ + g_warning ("playbin2 failed, falling back to playbin"); + worker->pipeline = gst_element_factory_make("playbin", + "playbin"); + + if (worker->pipeline) { + /* Use nwqueue only for non-rtsp and non-mms(h) + streams. */ + gboolean use_nw; + use_nw = worker->media.location && + !g_str_has_prefix(worker->media.location, + "rtsp://") && + !g_str_has_prefix(worker->media.location, + "mms://") && + !g_str_has_prefix(worker->media.location, + "mmsh://"); + + g_debug("playbin using network queue: %d", use_nw); + + /* These need a modified version of playbin. */ + g_object_set(G_OBJECT(worker->pipeline), + "nw-queue", use_nw, NULL); + g_object_set(G_OBJECT(worker->pipeline), + "no-video-transform", TRUE, NULL); + } + } + + if (!worker->pipeline) { + g_critical("failed to create playback pipeline"); + g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), + "error", + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM, + "Could not create pipeline"); + g_assert_not_reached(); + } + + + worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline)); + gst_bus_set_sync_handler(worker->bus, + (GstBusSyncHandler)_sync_bus_handler, worker); + worker->async_bus_id = gst_bus_add_watch_full(worker->bus,G_PRIORITY_HIGH, + (GstBusFunc)_async_bus_handler, + worker, NULL); + + /* Listen for changes in stream-info object to find out whether the + * media contains video and throw error if application has not provided + * video window. */ + g_signal_connect(worker->pipeline, "notify::stream-info", + G_CALLBACK(_stream_info_cb), worker); + +#ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME + g_object_set(worker->pipeline, "flags", 99, NULL); + + /* Set audio and video sinks ourselves. We create and configure + them only once. */ + if (!worker->asink) { + worker->asink = gst_element_factory_make("pulsesink", NULL); + if (!worker->asink) { + g_critical("Failed to create pipeline audio sink"); + g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), + "error", + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM, + "Could not create audio sink"); + g_assert_not_reached(); + } + gst_object_ref(worker->asink); + g_object_set(worker->asink, "buffer-time", + (gint64) MAFW_GST_BUFFER_TIME, NULL); + g_object_set(worker->asink, "latency-time", + (gint64) MAFW_GST_LATENCY_TIME, NULL); + } + g_object_set(worker->pipeline, "audio-sink", worker->asink, NULL); +#endif + + if (!worker->vsink) { + worker->vsink = gst_element_factory_make("xvimagesink", NULL); + if (!worker->vsink) { + g_critical("Failed to create pipeline video sink"); + g_signal_emit_by_name(MAFW_EXTENSION (worker->owner), + "error", + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_UNABLE_TO_PERFORM, + "Could not create video sink"); + g_assert_not_reached(); + } + gst_object_ref(worker->vsink); + g_object_set(G_OBJECT(worker->vsink), "handle-events", + TRUE, NULL); + g_object_set(worker->vsink, "force-aspect-ratio", + TRUE, NULL); + } + g_object_set(worker->pipeline, "video-sink", worker->vsink, NULL); +} + +/* + * @seek_type: GstSeekType + * @position: Time in seconds where to seek + */ +static void _do_seek(MafwGstRendererWorker *worker, GstSeekType seek_type, + gint position, GError **error) +{ + gboolean ret; + gint64 spos; + + g_assert(worker != NULL); + + if (worker->eos || !worker->media.seekable) + goto err; + + /* According to the docs, relative seeking is not so easy: + GST_SEEK_TYPE_CUR - change relative to currently configured segment. + This can't be used to seek relative to the current playback position - + do a position query, calculate the desired position and then do an + absolute position seek instead if that's what you want to do. */ + if (seek_type == GST_SEEK_TYPE_CUR) + { + gint curpos = mafw_gst_renderer_worker_get_position(worker); + position = curpos + position; + seek_type = GST_SEEK_TYPE_SET; + } + + if (position < 0) { + position = 0; + } + + worker->seek_position = position; + worker->report_statechanges = FALSE; + spos = (gint64)position * GST_SECOND; + g_debug("seek: type = %d, offset = %lld", seek_type, spos); + + /* If the pipeline has been set to READY by us, then wake it up by + setting it to PAUSED (when we get the READY->PAUSED transition + we will execute the seek). This way when we seek we disable the + READY state (logical, since the player is not idle anymore) + allowing the sink to render the destination frame in case of + video playback */ + if (worker->in_ready && worker->state == GST_STATE_READY) { + gst_element_set_state(worker->pipeline, GST_STATE_PAUSED); + } else { + ret = gst_element_seek(worker->pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_KEY_UNIT, + seek_type, spos, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (!ret) { + /* Seeking is async, so seek_position should not be + invalidated here */ + goto err; + } + } + return; + +err: g_set_error(error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_CANNOT_SET_POSITION, + "Seeking to %d failed", position); +} + +/* @vol should be between [0 .. 100], higher values (up to 1000) are allowed, + * but probably cause distortion. */ +void mafw_gst_renderer_worker_set_volume( + MafwGstRendererWorker *worker, guint volume) +{ + _set_volume(worker, CLAMP((gdouble)volume / 100.0, 0.0, 1.0)); +} + +guint mafw_gst_renderer_worker_get_volume( + MafwGstRendererWorker *worker) +{ + return (guint) + (mafw_gst_renderer_worker_volume_get(worker->wvolume) * 100); +} + +void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker, + gboolean mute) +{ + _set_mute(worker, mute); +} + +gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker) +{ + return mafw_gst_renderer_worker_volume_is_muted(worker->wvolume); +} + +#ifdef HAVE_GDKPIXBUF +void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker, + gboolean current_frame_on_pause) +{ + worker->current_frame_on_pause = current_frame_on_pause; +} + +gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker) +{ + return worker->current_frame_on_pause; +} +#endif + +void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, GError **error) +{ + /* If player is paused and we have a timeout for going to ready + * restart it. This is logical, since the user is seeking and + * thus, the player is not idle anymore. Also this prevents that + * when seeking streams we enter buffering and in the middle of + * the buffering process we set the pipeline to ready (which stops + * the buffering before it reaches 100%, making the client think + * buffering is still going on). + */ + if (worker->ready_timeout) { + _remove_ready_timeout(worker); + _add_ready_timeout(worker); + } + + _do_seek(worker, seek_type, position, error); + if (worker->notify_seek_handler) + worker->notify_seek_handler(worker, worker->owner); +} + +/* + * Gets current position, rounded down into precision of one second. If a seek + * is pending, returns the position we are going to seek. Returns -1 on + * failure. + */ +gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker) +{ + GstFormat format; + gint64 time = 0; + g_assert(worker != NULL); + + /* If seek is ongoing, return the position where we are seeking. */ + if (worker->seek_position != -1) + { + return worker->seek_position; + } + /* Otherwise query position from pipeline. */ + format = GST_FORMAT_TIME; + if (worker->pipeline && + gst_element_query_position(worker->pipeline, &format, &time)) + { + return (gint)(NSECONDS_TO_SECONDS(time)); + } + return -1; +} + +GHashTable *mafw_gst_renderer_worker_get_current_metadata( + MafwGstRendererWorker *worker) +{ + return worker->current_metadata; +} + +void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid) +{ + /* Check for errors on the target window */ + XSetErrorHandler(xerror); + + /* Store the target window id */ + g_debug("Setting xid: %x", (guint)xid); + worker->xid = xid; + + /* Check if we should use it right away */ + mafw_gst_renderer_worker_apply_xid(worker); +} + +XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker) +{ + return worker->xid; +} + +gboolean mafw_gst_renderer_worker_get_autopaint( + MafwGstRendererWorker *worker) +{ + return worker->autopaint; +} +void mafw_gst_renderer_worker_set_autopaint( + MafwGstRendererWorker *worker, gboolean autopaint) +{ + worker->autopaint = autopaint; + if (worker->vsink) + g_object_set(worker->vsink, "autopaint-colorkey", + autopaint, NULL); +} + +gint mafw_gst_renderer_worker_get_colorkey( + MafwGstRendererWorker *worker) +{ + return worker->colorkey; +} + +gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker) +{ + return worker->media.seekable; +} + +static void _play_pl_next(MafwGstRendererWorker *worker) { + gchar *next; + + g_assert(worker != NULL); + g_return_if_fail(worker->pl.items != NULL); + + next = (gchar *) g_slist_nth_data(worker->pl.items, + ++worker->pl.current); + mafw_gst_renderer_worker_stop(worker); + _reset_media_info(worker); + + worker->media.location = g_strdup(next); + _construct_pipeline(worker); + _start_play(worker); +} + +static void _on_pl_entry_parsed(TotemPlParser *parser, gchar *uri, + gpointer metadata, gpointer user_data) +{ + MafwGstRendererWorker *worker = user_data; + + if (uri != NULL) { + worker->pl.items = + g_slist_append(worker->pl.items, g_strdup(uri)); + } +} + +static void _do_play(MafwGstRendererWorker *worker) +{ + g_assert(worker != NULL); + + if (worker->pipeline == NULL) { + g_debug("play without a pipeline!"); + return; + } + worker->report_statechanges = TRUE; + + /* If we have to stay paused, we do and add the ready + * timeout. Otherwise, we move the pipeline */ + if (!worker->stay_paused) { + /* If pipeline is READY, we move it to PAUSED, + * otherwise, to PLAYING */ + if (worker->state == GST_STATE_READY) { + gst_element_set_state(worker->pipeline, + GST_STATE_PAUSED); + g_debug("setting pipeline to PAUSED"); + } else { + _reset_volume_and_mute_to_pipeline(worker); + gst_element_set_state(worker->pipeline, + GST_STATE_PLAYING); + g_debug("setting pipeline to PLAYING"); + } + } + else { + g_debug("staying in PAUSED state"); + _add_ready_timeout(worker); + } +} + +void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker, + const gchar *uri) +{ + g_assert(uri); + + mafw_gst_renderer_worker_stop(worker); + _reset_media_info(worker); + _reset_pl_info(worker); + /* Check if the item to play is a single item or a playlist. */ + if (uri_is_playlist(uri)){ + /* In case of a playlist we parse it and start playing the first + item of the playlist. */ + TotemPlParser *pl_parser; + gchar *item; + + /* Initialize the playlist parser */ + pl_parser = totem_pl_parser_new (); + g_object_set(pl_parser, "recurse", TRUE, "disable-unsafe", + TRUE, NULL); + g_signal_connect(G_OBJECT(pl_parser), "entry-parsed", + G_CALLBACK(_on_pl_entry_parsed), worker); + + /* Parsing */ + if (totem_pl_parser_parse(pl_parser, uri, FALSE) != + TOTEM_PL_PARSER_RESULT_SUCCESS) { + /* An error happens while parsing */ + _send_error(worker, + g_error_new(MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_PLAYLIST_PARSING, + "Playlist parsing failed: %s", + uri)); + return; + } + + if (!worker->pl.items) { + /* The playlist is empty */ + _send_error(worker, + g_error_new(MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_PLAYLIST_PARSING, + "The playlist %s is empty.", + uri)); + return; + } + + /* Set the playback mode */ + worker->mode = WORKER_MODE_PLAYLIST; + worker->pl.notify_play_pending = TRUE; + + /* Set the item to be played */ + worker->pl.current = 0; + item = (gchar *) g_slist_nth_data(worker->pl.items, 0); + worker->media.location = g_strdup(item); + + /* Free the playlist parser */ + g_object_unref(pl_parser); + } else { + /* Single item. Set the playback mode according to that */ + worker->mode = WORKER_MODE_SINGLE_PLAY; + + /* Set the item to be played */ + worker->media.location = g_strdup(uri); + } + _construct_pipeline(worker); + _start_play(worker); +} + +void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker, + gchar **uris) +{ + gint i; + gchar *item; + + g_assert(uris && uris[0]); + + mafw_gst_renderer_worker_stop(worker); + _reset_media_info(worker); + _reset_pl_info(worker); + + /* Add the uris to playlist */ + i = 0; + while (uris[i]) { + worker->pl.items = + g_slist_append(worker->pl.items, g_strdup(uris[i])); + i++; + } + + /* Set the playback mode */ + worker->mode = WORKER_MODE_REDUNDANT; + worker->pl.notify_play_pending = TRUE; + + /* Set the item to be played */ + worker->pl.current = 0; + item = (gchar *) g_slist_nth_data(worker->pl.items, 0); + worker->media.location = g_strdup(item); + + /* Start playing */ + _construct_pipeline(worker); + _start_play(worker); +} + +/* + * Currently, stop destroys the Gst pipeline and resets the worker into + * default startup configuration. + */ +void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker) +{ + g_debug("worker stop"); + g_assert(worker != NULL); + + /* If location is NULL, this is a pre-created pipeline */ + if (worker->async_bus_id && worker->pipeline && !worker->media.location) + return; + + if (worker->pipeline) { + g_debug("destroying pipeline"); + if (worker->async_bus_id) { + g_source_remove(worker->async_bus_id); + worker->async_bus_id = 0; + } + gst_bus_set_sync_handler(worker->bus, NULL, NULL); + gst_element_set_state(worker->pipeline, GST_STATE_NULL); + if (worker->bus) { + gst_object_unref(GST_OBJECT_CAST(worker->bus)); + worker->bus = NULL; + } + gst_object_unref(GST_OBJECT(worker->pipeline)); + worker->pipeline = NULL; + } + + /* Reset worker */ + worker->report_statechanges = TRUE; + worker->state = GST_STATE_NULL; + worker->prerolling = FALSE; + worker->is_live = FALSE; + worker->buffering = FALSE; + worker->is_stream = FALSE; + worker->is_error = FALSE; + worker->eos = FALSE; + worker->seek_position = -1; + _remove_ready_timeout(worker); + _free_taglist(worker); + if (worker->current_metadata) { + g_hash_table_destroy(worker->current_metadata); + worker->current_metadata = NULL; + } + + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + worker->duration_seek_timeout = 0; + } + + /* Reset media iformation */ + _reset_media_info(worker); + + /* We are not playing, so we can let the screen blank */ + blanking_allow(); + + /* And now get a fresh pipeline ready */ + _construct_pipeline(worker); +} + +void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker) +{ + g_assert(worker != NULL); + + if (worker->buffering && worker->state == GST_STATE_PAUSED && + !worker->prerolling) { + /* If we are buffering and get a pause, we have to + * signal state change and stay_paused */ + g_debug("Pausing while buffering, signalling state change"); + worker->stay_paused = TRUE; + if (worker->notify_pause_handler) { + worker->notify_pause_handler( + worker, + worker->owner); + } + } else { + worker->report_statechanges = TRUE; + + if (gst_element_set_state(worker->pipeline, GST_STATE_PAUSED) == + GST_STATE_CHANGE_ASYNC) + { + /* XXX this blocks at most 2 seconds. */ + gst_element_get_state(worker->pipeline, NULL, NULL, + 2 * GST_SECOND); + } + blanking_allow(); + } +} + +void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker) +{ + if (worker->mode == WORKER_MODE_PLAYLIST || + worker->mode == WORKER_MODE_REDUNDANT) { + /* We must notify play if the "playlist" playback + is resumed */ + worker->pl.notify_play_pending = TRUE; + } + if (worker->buffering && worker->state == GST_STATE_PAUSED && + !worker->prerolling) { + /* If we are buffering we cannot resume, but we know + * that the pipeline will be moved to PLAYING as + * stay_paused is FALSE, so we just activate the state + * change report, this way as soon as buffering is finished + * the pipeline will be set to PLAYING and the state + * change will be reported */ + worker->report_statechanges = TRUE; + g_debug("Resumed while buffering, activating pipeline state " + "changes"); + /* Notice though that we can receive the Resume before + we get any buffering information. In that case + we go with the "else" branch and set the pipeline to + to PLAYING. However, it is possible that in this case + we get the fist buffering signal before the + PAUSED -> PLAYING state change. In that case, since we + ignore state changes while buffering we never signal + the state change to PLAYING. We can only fix this by + checking, when we receive a PAUSED -> PLAYING transition + if we are buffering, and in that case signal the state + change (if we get that transition while buffering + is on, it can only mean that the client resumed playback + while buffering, and we must notify the state change) */ + } else { + _do_play(worker); + } +} + +static void _volume_init_cb(MafwGstRendererWorkerVolume *wvolume, + gpointer data) +{ + MafwGstRendererWorker *worker = data; + gdouble volume; + gboolean mute; + + worker->wvolume = wvolume; + + g_debug("volume manager initialized"); + + volume = mafw_gst_renderer_worker_volume_get(wvolume); + mute = mafw_gst_renderer_worker_volume_is_muted(wvolume); + _volume_cb(wvolume, volume, worker); + _mute_cb(wvolume, mute, worker); +} + +MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner) +{ + MafwGstRendererWorker *worker; + GMainContext *main_context; + + worker = g_new0(MafwGstRendererWorker, 1); + worker->mode = WORKER_MODE_SINGLE_PLAY; + worker->pl.items = NULL; + worker->pl.current = 0; + worker->pl.notify_play_pending = TRUE; + worker->owner = owner; + worker->report_statechanges = TRUE; + worker->state = GST_STATE_NULL; + worker->seek_position = -1; + worker->ready_timeout = 0; + worker->in_ready = FALSE; + worker->xid = 0; + worker->autopaint = TRUE; + worker->colorkey = -1; + worker->vsink = NULL; + worker->asink = NULL; + worker->tag_list = NULL; + worker->current_metadata = NULL; + +#ifdef HAVE_GDKPIXBUF + worker->current_frame_on_pause = FALSE; + _init_tmp_files_pool(worker); +#endif + worker->notify_seek_handler = NULL; + worker->notify_pause_handler = NULL; + worker->notify_play_handler = NULL; + worker->notify_buffer_status_handler = NULL; + worker->notify_eos_handler = NULL; + worker->notify_error_handler = NULL; + Global_worker = worker; + main_context = g_main_context_default(); + worker->wvolume = NULL; + mafw_gst_renderer_worker_volume_init(main_context, + _volume_init_cb, worker, + _volume_cb, worker, + _mute_cb, worker); + blanking_init(); + _construct_pipeline(worker); + + return worker; +} + +void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker) +{ + blanking_deinit(); +#ifdef HAVE_GDKPIXBUF + _destroy_tmp_files_pool(worker); +#endif + mafw_gst_renderer_worker_volume_destroy(worker->wvolume); + mafw_gst_renderer_worker_stop(worker); +} +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer-worker.h b/libmafw-gst-renderer/mafw-gst-renderer-worker.h new file mode 100644 index 0000000..d59c09a --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer-worker.h @@ -0,0 +1,210 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef MAFW_GST_RENDERER_WORKER_H +#define MAFW_GST_RENDERER_WORKER_H + +#include +#include +#include +#include "mafw-gst-renderer-worker-volume.h" + +#define MAFW_GST_RENDERER_MAX_TMP_FILES 5 + +typedef struct _MafwGstRendererWorker MafwGstRendererWorker; + +typedef void (*MafwGstRendererWorkerNotifySeekCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyPauseCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyPlayCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyBufferStatusCb)(MafwGstRendererWorker *worker, gpointer owner, gdouble percent); +typedef void (*MafwGstRendererWorkerNotifyEOSCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyErrorCb)(MafwGstRendererWorker *worker, + gpointer owner, + const GError *error); + +typedef enum { + WORKER_MODE_SINGLE_PLAY, + WORKER_MODE_PLAYLIST, + WORKER_MODE_REDUNDANT, +} PlaybackMode; + +typedef enum { + SEEKABILITY_UNKNOWN = -1, + SEEKABILITY_NO_SEEKABLE, + SEEKABILITY_SEEKABLE, +} SeekabilityType; + +/* + * media: Information about currently selected media. + * location: Current media location + * length_nanos: Length of the media, in nanoseconds + * has_visual_content: the clip contains some visual content (video) + * video_width: If media contains video, this tells the video width + * video_height: If media contains video, this tells the video height + * seekable: Tells whether the media can be seeked + * par_n: Video pixel aspect ratio numerator + * par_d: Video pixel aspect ratio denominator + * owner: Owner of the worker; usually a MafwGstRenderer (FIXME USUALLY?) + * pipeline: Playback pipeline + * bus: Message bus + * state: Current playback pipeline state + * is_stream: Is currently playing media a stream + * muted: Is the audio muted + * eos: Has playback reached EOS already + * is_error: Has there been an error situation + * buffering: Indicates the buffering state + * prerolling: Indicates the prerolling state (NULL -> PAUSED) + * report_statechanges: Report state change bus messages + * current_volume: Current audio volume [0.0 .. 1.0], see playbin:volume + * async_bus_id: ID handle for GstBus + * buffer_probe_id: ID of the video renderer buffer probe + * seek_position: Indicates the pos where to seek, in seconds + * vsink: Video sink element of the pipeline + * asink: Audio sink element of the pipeline + * xid: XID for video playback + * current_frame_on_pause: whether to emit current frame when pausing + */ +struct _MafwGstRendererWorker { + struct { + gchar *location; + gint64 length_nanos; + gboolean has_visual_content; + gint video_width; + gint video_height; + gdouble fps; + SeekabilityType seekable; + gint par_n; + gint par_d; + } media; + PlaybackMode mode; + struct { + GSList *items; + gint current; + gboolean notify_play_pending; + } pl; + gpointer owner; + GstElement *pipeline; + GstBus *bus; + /* GStreamer state we are considering right now */ + GstState state; + MafwGstRendererWorkerVolume *wvolume; + gboolean is_stream; + gboolean muted; + /* we are handing eos or we did */ + gboolean eos; + /* if we are handling (or handled) and error */ + gboolean is_error; + /* pipeline is buffering */ + gboolean buffering; + /* pipeline is prerolling */ + gboolean prerolling; + /* stream is live and doesn't need prerolling */ + gboolean is_live; + /* if we have to stay in paused though a do_play was + * requested. Usually used when pausing in transitioning */ + gboolean stay_paused; + /* this variable should be FALSE while we are hiding state + * changed to the UI. This is that GStreamer can perform + * state_changes without us requiring it, for example, then + * seeking, buffering and so on and we have to hide those + * changes */ + gboolean report_statechanges; + guint async_bus_id; + gint seek_position; + guint ready_timeout; + guint duration_seek_timeout; + /* After some time PAUSED, we set the pipeline to READY in order to + * save resources. This field states if we are in this special + * situation. + * It is set to TRUE when the state change to READY is requested + * and stays like that until we reach again PLAYING state (not PAUSED). + * The reason for this is that when resuming streams, we have to + * move from READY to PAUSED, then seek to the position where the + * stream had been paused, then wait for buffering to finish, and then + * play (and notify the state change to PLAYING), and we have to + * differentiate this case from the one in which we have entered PAUSED + * silently (when we ran out of buffer while playing, because in that + * case, when we are done buffering we want to resume playback silently + * again. + */ + gboolean in_ready; + GstElement *vsink; + GstElement *asink; + XID xid; + gboolean autopaint; + gint colorkey; + GPtrArray *tag_list; + GHashTable *current_metadata; + +#ifdef HAVE_GDKPIXBUF + gboolean current_frame_on_pause; + gchar *tmp_files_pool[MAFW_GST_RENDERER_MAX_TMP_FILES]; + guint8 tmp_files_pool_index; +#endif + + /* Handlers for notifications */ + MafwGstRendererWorkerNotifySeekCb notify_seek_handler; + MafwGstRendererWorkerNotifyPauseCb notify_pause_handler; + MafwGstRendererWorkerNotifyPlayCb notify_play_handler; + MafwGstRendererWorkerNotifyBufferStatusCb notify_buffer_status_handler; + MafwGstRendererWorkerNotifyEOSCb notify_eos_handler; + MafwGstRendererWorkerNotifyErrorCb notify_error_handler; +}; + +G_BEGIN_DECLS + +MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner); +void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_volume(MafwGstRendererWorker *worker, + guint vol); +guint mafw_gst_renderer_worker_get_volume(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker, + gboolean mute); +gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker); +#ifdef HAVE_GDKPIXBUF +void mafw_gst_renderer_worker_set_current_frame_on_pause(MafwGstRendererWorker *worker, + gboolean current_frame_on_pause); +gboolean mafw_gst_renderer_worker_get_current_frame_on_pause(MafwGstRendererWorker *worker); +#endif +void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, + GError **error); +gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid); +XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker); +gboolean mafw_gst_renderer_worker_get_autopaint(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_set_autopaint(MafwGstRendererWorker *worker, gboolean autopaint); +gint mafw_gst_renderer_worker_get_colorkey(MafwGstRendererWorker *worker); +gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker); +GHashTable *mafw_gst_renderer_worker_get_current_metadata(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker, const gchar *uri); +void mafw_gst_renderer_worker_play_alternatives(MafwGstRendererWorker *worker, gchar **uris); +void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker); + +G_END_DECLS +#endif +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer.c b/libmafw-gst-renderer/mafw-gst-renderer.c new file mode 100644 index 0000000..6412b13 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer.c @@ -0,0 +1,2191 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include "mafw-gst-renderer.h" +#include "mafw-gst-renderer-utils.h" +#include "mafw-gst-renderer-worker.h" + +#include "mafw-gst-renderer-state-playing.h" +#include "mafw-gst-renderer-state-stopped.h" +#include "mafw-gst-renderer-state-paused.h" +#include "mafw-gst-renderer-state-transitioning.h" + +#include "blanking.h" + +#ifdef HAVE_CONIC +#include +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer" + +#define is_current_uri_stream(self) \ + (((self)->media != NULL) && ((self)->media->uri != NULL) && \ + uri_is_stream((self)->media->uri)) + +#define GCONF_OSSO_AF "/system/osso/af" +#define GCONF_BATTERY_COVER_OPEN "/system/osso/af/mmc-cover-open" +#define HAL_VIDEOOUT_UDI "/org/freedesktop/Hal/devices" \ + "/platform_soc_audio_logicaldev_input" + +/*---------------------------------------------------------------------------- + Static variable definitions + ----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------- + Plugin initialization + ----------------------------------------------------------------------------*/ + +static gboolean mafw_gst_renderer_initialize(MafwRegistry *registry, + GError **error); +static void mafw_gst_renderer_deinitialize(GError **error); + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +static void mafw_gst_renderer_dispose(GObject *object); +static void mafw_gst_renderer_finalize(GObject *object); + +/*---------------------------------------------------------------------------- + Hal callbacks + ----------------------------------------------------------------------------*/ +static void _property_modified(LibHalContext *ctx, const char *udi, + const char *key, dbus_bool_t is_removed, + dbus_bool_t is_added); +static gboolean _tv_out_is_connected(LibHalContext *ctx, const char *udi); + +/*---------------------------------------------------------------------------- + GConf notifications + ----------------------------------------------------------------------------*/ + +static void _battery_cover_open_cb(GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + MafwGstRenderer *renderer); + +/*---------------------------------------------------------------------------- + Gnome VFS notifications + ----------------------------------------------------------------------------*/ + +static void _volume_pre_unmount_cb(GnomeVFSVolumeMonitor *monitor, + GnomeVFSVolume *volume, + MafwGstRenderer *renderer); + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +static void _signal_state_changed(MafwGstRenderer * self); +static void _signal_media_changed(MafwGstRenderer * self); +static void _signal_playlist_changed(MafwGstRenderer * self); +static void _signal_transport_actions_property_changed(MafwGstRenderer * self); + +/*---------------------------------------------------------------------------- + Properties + ----------------------------------------------------------------------------*/ + +static void _set_error_policy(MafwGstRenderer *renderer, MafwRendererErrorPolicy policy); +static MafwRendererErrorPolicy _get_error_policy(MafwGstRenderer *renderer); + +static void mafw_gst_renderer_set_property(MafwExtension *self, const gchar *key, + const GValue *value); +static void mafw_gst_renderer_get_property(MafwExtension *self, const gchar *key, + MafwExtensionPropertyCallback callback, + gpointer user_data); + +/*---------------------------------------------------------------------------- + Metadata + ----------------------------------------------------------------------------*/ + +static void _notify_metadata(MafwSource *cb_source, + const gchar *cb_object_id, + GHashTable *cb_metadata, + gpointer cb_user_data, + const GError *cb_error); + +/*---------------------------------------------------------------------------- + Notification operations + ----------------------------------------------------------------------------*/ + +static void _notify_play(MafwGstRendererWorker *worker, gpointer owner); +static void _notify_pause(MafwGstRendererWorker *worker, gpointer owner); +static void _notify_seek(MafwGstRendererWorker *worker, gpointer owner); +static void _notify_buffer_status(MafwGstRendererWorker *worker, gpointer owner, + gdouble percent); +static void _notify_eos(MafwGstRendererWorker *worker, gpointer owner); +static void _error_handler(MafwGstRendererWorker *worker, gpointer owner, + const GError *error); + +#ifdef HAVE_CONIC +/*---------------------------------------------------------------------------- + Connection + ----------------------------------------------------------------------------*/ + +static void _connection_init(MafwGstRenderer *renderer); +#endif + +/*---------------------------------------------------------------------------- + Plugin initialization + ----------------------------------------------------------------------------*/ + +/* + * Registers the plugin descriptor making this plugin available to the + * framework and applications + */ +G_MODULE_EXPORT MafwPluginDescriptor mafw_gst_renderer_plugin_description = { + { .name = MAFW_GST_RENDERER_PLUGIN_NAME }, + .initialize = mafw_gst_renderer_initialize, + .deinitialize = mafw_gst_renderer_deinitialize, +}; + +static gboolean mafw_gst_renderer_initialize(MafwRegistry *registry, + GError **error) +{ + MafwGstRenderer *self; + + g_assert(registry != NULL); + self = MAFW_GST_RENDERER(mafw_gst_renderer_new(registry)); + mafw_registry_add_extension(registry, MAFW_EXTENSION(self)); + + return TRUE; +} + +static void mafw_gst_renderer_deinitialize(GError **error) +{ +} + +/*---------------------------------------------------------------------------- + GObject initialization + ----------------------------------------------------------------------------*/ + +G_DEFINE_TYPE(MafwGstRenderer, mafw_gst_renderer, MAFW_TYPE_RENDERER); + +static void mafw_gst_renderer_class_init(MafwGstRendererClass *klass) +{ + GObjectClass *gclass = NULL; + MafwRendererClass *renderer_class = NULL; + const gchar *preloaded_plugins[] = {"playback", "uridecodebin", + "coreelements", "typefindfunctions", "omx", "selector", + "autodetect", "pulseaudio", "audioconvert", "audioresample", + "xvimagesink", "ffmpegcolorspace", "videoscale", NULL}; + gint i = 0; + GObject *plugin; + + gclass = G_OBJECT_CLASS(klass); + g_return_if_fail(gclass != NULL); + + renderer_class = MAFW_RENDERER_CLASS(klass); + g_return_if_fail(renderer_class != NULL); + + /* GObject */ + + gclass->dispose = mafw_gst_renderer_dispose; + gclass->finalize = mafw_gst_renderer_finalize; + + /* Playback */ + + renderer_class->play = mafw_gst_renderer_play; + renderer_class->play_object = mafw_gst_renderer_play_object; + renderer_class->stop = mafw_gst_renderer_stop; + renderer_class->pause = mafw_gst_renderer_pause; + renderer_class->resume = mafw_gst_renderer_resume; + renderer_class->get_status = mafw_gst_renderer_get_status; + + /* Playlist operations */ + + renderer_class->assign_playlist = mafw_gst_renderer_assign_playlist; + renderer_class->next = mafw_gst_renderer_next; + renderer_class->previous = mafw_gst_renderer_previous; + renderer_class->goto_index = mafw_gst_renderer_goto_index; + + /* Playback position */ + + renderer_class->set_position = mafw_gst_renderer_set_position; + renderer_class->get_position = mafw_gst_renderer_get_position; + + /* Metadata */ + + renderer_class->get_current_metadata = + mafw_gst_renderer_get_current_metadata; + + /* Properties */ + + MAFW_EXTENSION_CLASS(klass)->get_extension_property = + (gpointer) mafw_gst_renderer_get_property; + MAFW_EXTENSION_CLASS(klass)->set_extension_property = + (gpointer) mafw_gst_renderer_set_property; + + gst_init(NULL, NULL); + gst_pb_utils_init(); + + /* Pre-load some common plugins */ + while (preloaded_plugins[i]) + { + plugin = G_OBJECT(gst_plugin_load_by_name(preloaded_plugins[i])); + if (plugin) + g_object_unref(plugin); + else + g_debug("Can not load plugin: %s", preloaded_plugins[i]); + i++; + } +} + +static void mafw_gst_renderer_init(MafwGstRenderer *self) +{ + MafwGstRenderer *renderer = NULL; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + renderer = MAFW_GST_RENDERER(self); + g_return_if_fail(renderer != NULL); + + mafw_extension_add_property(MAFW_EXTENSION(self), "volume", G_TYPE_UINT); + mafw_extension_add_property(MAFW_EXTENSION(self), "mute", G_TYPE_BOOLEAN); + mafw_extension_add_property(MAFW_EXTENSION(self), "xid", G_TYPE_UINT); + mafw_extension_add_property(MAFW_EXTENSION(self), "error-policy", G_TYPE_UINT); + MAFW_EXTENSION_SUPPORTS_AUTOPAINT(self); + MAFW_EXTENSION_SUPPORTS_COLORKEY(self); +#ifdef HAVE_GDKPIXBUF + mafw_extension_add_property(MAFW_EXTENSION(self), + "current-frame-on-pause", + G_TYPE_BOOLEAN); +#endif + mafw_extension_add_property(MAFW_EXTENSION(self), + MAFW_PROPERTY_GST_RENDERER_TV_CONNECTED, + G_TYPE_BOOLEAN); + MAFW_EXTENSION_SUPPORTS_TRANSPORT_ACTIONS(self); + renderer->media = g_new0(MafwGstRendererMedia, 1); + renderer->media->seekability = SEEKABILITY_UNKNOWN; + renderer->current_state = Stopped; + + renderer->playlist = NULL; + renderer->iterator = NULL; + renderer->seeking_to = -1; + renderer->update_playcount_id = 0; + + self->worker = mafw_gst_renderer_worker_new(self); + + /* Set notification handlers for worker */ + renderer->worker->notify_play_handler = _notify_play; + renderer->worker->notify_pause_handler = _notify_pause; + renderer->worker->notify_seek_handler = _notify_seek; + renderer->worker->notify_error_handler = _error_handler; + renderer->worker->notify_eos_handler = _notify_eos; + renderer->worker->notify_buffer_status_handler = _notify_buffer_status; + + renderer->states = g_new0 (MafwGstRendererState*, _LastMafwPlayState); + renderer->states[Stopped] = + MAFW_GST_RENDERER_STATE(mafw_gst_renderer_state_stopped_new(self)); + renderer->states[Transitioning] = + MAFW_GST_RENDERER_STATE( + mafw_gst_renderer_state_transitioning_new(self)); + renderer->states[Playing] = + MAFW_GST_RENDERER_STATE(mafw_gst_renderer_state_playing_new(self)); + renderer->states[Paused] = + MAFW_GST_RENDERER_STATE(mafw_gst_renderer_state_paused_new(self)); + + renderer->current_state = Stopped; + renderer->resume_playlist = FALSE; + renderer->playback_mode = MAFW_GST_RENDERER_MODE_PLAYLIST; + +#ifdef HAVE_CONIC + renderer->connected = FALSE; + renderer->connection = NULL; + + _connection_init(renderer); +#endif + renderer->gconf_client = gconf_client_get_default(); + gconf_client_add_dir(renderer->gconf_client, GCONF_OSSO_AF, + GCONF_CLIENT_PRELOAD_ONELEVEL, &error); + if (error) { + g_warning("%s", error->message); + g_error_free(error); + error = NULL; + } + + gconf_client_notify_add(renderer->gconf_client, + GCONF_BATTERY_COVER_OPEN, + (GConfClientNotifyFunc) _battery_cover_open_cb, + renderer, + NULL, &error); + + if (error) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (gnome_vfs_init()) { + GnomeVFSVolumeMonitor *monitor = gnome_vfs_get_volume_monitor(); + g_signal_connect(monitor, "volume-pre-unmount", + G_CALLBACK(_volume_pre_unmount_cb), renderer); + } else { + g_warning("Failed to initialize gnome-vfs"); + } +} + +static void mafw_gst_renderer_dispose(GObject *object) +{ + MafwGstRenderer *renderer; + + g_return_if_fail(MAFW_IS_GST_RENDERER(object)); + + renderer = MAFW_GST_RENDERER(object); + + if (renderer->worker != NULL) { + mafw_gst_renderer_worker_exit(renderer->worker); + renderer->seek_pending = FALSE; + g_free(renderer->worker); + renderer->worker = NULL; + } + + if (renderer->registry != NULL) { + g_object_unref(renderer->registry); + renderer->registry = NULL; + } + + if (renderer->states != NULL) { + guint i = 0; + + for (i = 0; i < _LastMafwPlayState; i++) { + if (renderer->states[i] != NULL) + g_object_unref(renderer->states[i]); + } + g_free(renderer->states); + renderer->states = NULL; + } + + if (renderer->hal_ctx != NULL) { + libhal_device_remove_property_watch(renderer->hal_ctx, + HAL_VIDEOOUT_UDI, + NULL); + libhal_ctx_shutdown(renderer->hal_ctx, NULL); + libhal_ctx_free(renderer->hal_ctx); + } + +#ifdef HAVE_CONIC + if (renderer->connection != NULL) { + g_object_unref(renderer->connection); + renderer->connection = NULL; + } +#endif + + if (renderer->gconf_client != NULL) { + g_object_unref(renderer->gconf_client); + renderer->gconf_client = NULL; + } + + G_OBJECT_CLASS(mafw_gst_renderer_parent_class)->dispose(object); +} + +static void mafw_gst_renderer_finalize(GObject *object) +{ + MafwGstRenderer *self = (MafwGstRenderer*) object; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + mafw_gst_renderer_clear_media(self); + + if (self->media) + { + g_free(self->media); + self->media = NULL; + } + + G_OBJECT_CLASS(mafw_gst_renderer_parent_class)->finalize(object); +} + +/** + * mafw_gst_renderer_new: + * @registry: The registry that owns this renderer. + * + * Creates a new MafwGstRenderer object + */ +GObject *mafw_gst_renderer_new(MafwRegistry* registry) +{ + GObject* object; + LibHalContext *ctx; + DBusConnection *conn; + DBusError err; + char **jackets; + char **jack; + gint num_jacks; + + object = g_object_new(MAFW_TYPE_GST_RENDERER, + "uuid", MAFW_GST_RENDERER_UUID, + "name", MAFW_GST_RENDERER_NAME, + "plugin", MAFW_GST_RENDERER_PLUGIN_NAME, + NULL); + g_assert(object != NULL); + MAFW_GST_RENDERER(object)->registry = g_object_ref(registry); + + /* Set default error policy */ + MAFW_GST_RENDERER(object)->error_policy = + MAFW_RENDERER_ERROR_POLICY_CONTINUE; + + MAFW_GST_RENDERER(object)->tv_connected = FALSE; + + /* Setup hal connection for reacting usb cable connected event */ + dbus_error_init(&err); + conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + + if (dbus_error_is_set(&err)) { + g_warning("Couldn't setup HAL connection: %s", err.message); + dbus_error_free(&err); + + goto err1; + } + ctx = libhal_ctx_new(); + libhal_ctx_set_dbus_connection(ctx, conn); + libhal_ctx_set_user_data(ctx, object); + + if (libhal_ctx_init(ctx, &err) == FALSE) { + if (dbus_error_is_set(&err)) { + g_warning("Could not initialize hal: %s", err.message); + dbus_error_free(&err); + } else { + g_warning("Could not initialize hal"); + } + goto err2; + } + + libhal_device_add_property_watch(ctx, HAL_VIDEOOUT_UDI, &err); + + if (dbus_error_is_set(&err)) { + g_warning("Could not start watching usb device: %s", + err.message); + dbus_error_free(&err); + + goto err3; + } + libhal_ctx_set_device_property_modified(ctx, _property_modified); + + /* Initializes blanking policy */ + jackets = libhal_find_device_by_capability(ctx, + "input.jack.video-out", + &num_jacks, NULL); + if (jackets != NULL) { + jack = jackets; + while (*jack) { + if (_tv_out_is_connected(ctx, *jack)) { + MAFW_GST_RENDERER(object)->tv_connected = TRUE; + break; + } + jack++; + } + + blanking_control(*jack == NULL); + libhal_free_string_array(jackets); + } + + MAFW_GST_RENDERER(object)->hal_ctx = ctx; + + return object; +err3: + libhal_ctx_shutdown(ctx, NULL); +err2: + libhal_ctx_free(ctx); +err1: + return object; +} + +/** + * mafw_gst_renderer_error_quark: + * + * Fetches the quark representing the domain of the errors in the + * gst renderer + * + * Return value: a quark identifying the error domain of the + * #MafwGstRenderer objects. + * + **/ +GQuark mafw_gst_renderer_error_quark(void) +{ + return g_quark_from_static_string("mafw-gst-renderer-error-quark"); +} + +void mafw_gst_renderer_set_playback_mode(MafwGstRenderer *self, + MafwGstRendererPlaybackMode mode) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + self->playback_mode = mode; +} + +MafwGstRendererPlaybackMode mafw_gst_renderer_get_playback_mode( + MafwGstRenderer *self) +{ + g_return_val_if_fail(MAFW_IS_GST_RENDERER(self), + MAFW_GST_RENDERER_MODE_STANDALONE); + return self->playback_mode; +} + +/*---------------------------------------------------------------------------- + Set Media + ----------------------------------------------------------------------------*/ + +static MafwSource* _get_source(MafwGstRenderer *renderer, + const gchar *object_id) +{ + MafwSource* source; + gchar* sourceid = NULL; + + g_assert(object_id != NULL); + + /* Attempt to find a source that provided the object ID */ + mafw_source_split_objectid(object_id, &sourceid, NULL); + source = MAFW_SOURCE(mafw_registry_get_extension_by_uuid( + renderer->registry, sourceid)); + g_free(sourceid); + + return source; +} + +void mafw_gst_renderer_get_metadata(MafwGstRenderer* self, + const gchar* objectid, + GError **error) +{ + MafwSource* source; + + g_assert(self != NULL); + + /* + * Any error here is an error when trying to Play, so + * it must be handled by error policy. + * Problem: if we get an error here and we are not in + * Transitioning yet (maybe we are still in Stopped state) + * then the policy may move to next and stay Stopped (instead of + * trying to play), so errors need to be handled by the policy + * in an idle callback, so that any error that may happen here + * is not processed until we have moved to Transitioning state + */ + + source = _get_source(self, objectid); + if (source != NULL) + { + /* List of metadata keys that we are interested in when going to + Transitioning state */ + static const gchar * const keys[] = + { MAFW_METADATA_KEY_URI, + MAFW_METADATA_KEY_IS_SEEKABLE, + MAFW_METADATA_KEY_DURATION, + NULL }; + + /* Source found, get metadata */ + mafw_source_get_metadata(source, objectid, + keys, + _notify_metadata, + self); + + } + else + { + /* This is a playback error: execute error policy */ + MafwGstRendererErrorClosure *error_closure; + error_closure = g_new0(MafwGstRendererErrorClosure, 1); + error_closure->renderer = self; + g_set_error (&(error_closure->error), + MAFW_EXTENSION_ERROR, + MAFW_EXTENSION_ERROR_EXTENSION_NOT_AVAILABLE, + "Unable to find source for current object ID"); + g_idle_add(mafw_gst_renderer_manage_error_idle, error_closure); + } +} + +void mafw_gst_renderer_set_object(MafwGstRenderer *self, const gchar *object_id) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(object_id != NULL); + + /* This is intended to be called only when using play_object(), + * as for playlists we use set_media_playlist() + */ + + /* Stop any ongoing playback */ + mafw_gst_renderer_clear_media(renderer); + + /* Set new object */ + renderer->media->object_id = g_strdup(object_id); + + /* Signal media changed */ + _signal_media_changed(renderer); +} + + +/** + * mafw_gst_renderer_clear_media: + * + * @renderer A #MafwGstRenderer whose media to clear + * + * Clears & frees the renderer's current media details + **/ +void mafw_gst_renderer_clear_media(MafwGstRenderer *self) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(self->media != NULL); + + g_free(self->media->object_id); + self->media->object_id = NULL; + + g_free(self->media->uri); + self->media->uri = NULL; + + g_free(self->media->title); + self->media->title = NULL; + + g_free(self->media->artist); + self->media->artist = NULL; + + g_free(self->media->album); + self->media->album = NULL; + + self->media->duration = 0; + self->media->position = 0; +} + + +/** + * mafw_gst_renderer_set_media_playlist: + * + * @self A #MafwGstRenderer, whose media to set + * + * Set current media from the renderer's playlist, using the current playlist index. + **/ +void mafw_gst_renderer_set_media_playlist(MafwGstRenderer* self) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + /* Get rid of old media details */ + mafw_gst_renderer_clear_media(self); + + if (self->playlist != NULL && + mafw_playlist_iterator_get_size(self->iterator, NULL) > 0) { + /* Get the current item from playlist */ + self->media->object_id = + g_strdup(mafw_playlist_iterator_get_current_objectid(self->iterator)); + } else { + self->media->object_id = NULL; + } + + _signal_media_changed(self); +} + +#ifdef HAVE_CONIC +/*---------------------------------------------------------------------------- + Connection + ----------------------------------------------------------------------------*/ + +static void +_con_ic_status_handler(ConIcConnection *conn, ConIcConnectionEvent *event, + gpointer data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) data; + + g_assert(MAFW_IS_GST_RENDERER(renderer)); + + renderer->connected = + con_ic_connection_event_get_status(event) == + CON_IC_STATUS_CONNECTED; +} + +static void +_connection_init(MafwGstRenderer *renderer) +{ + g_assert (MAFW_IS_GST_RENDERER(renderer)); + + if (renderer->connection == NULL) { + renderer->connection = con_ic_connection_new(); + renderer->connected = FALSE; + + g_assert(renderer->connection != NULL); + } + + g_object_set(renderer->connection, "automatic-connection-events", + TRUE, NULL); + g_signal_connect(renderer->connection, "connection-event", + G_CALLBACK (_con_ic_status_handler), renderer); + + con_ic_connection_connect(renderer->connection, + CON_IC_CONNECT_FLAG_AUTOMATICALLY_TRIGGERED); +} +#endif + +/*---------------------------------------------------------------------------- + Hal callbacks + ----------------------------------------------------------------------------*/ + +static gboolean _tv_out_is_connected(LibHalContext *ctx, const char *udi) +{ + gboolean is_tv_out_jack = FALSE; + char **jack_types; + char **jack; + + if (udi == NULL) { + return FALSE; + } + + jack_types = libhal_device_get_property_strlist(ctx, udi, + "input.jack.type", + NULL); + if (jack_types == NULL) { + return FALSE; + } + + jack = jack_types; + while (*jack) { + if (strcmp(*jack, "video-out") == 0) { + is_tv_out_jack = TRUE; + break; + } else { + jack++; + } + } + + libhal_free_string_array(jack_types); + + return is_tv_out_jack; +} + +static void _property_modified(LibHalContext *ctx, const char *udi, + const char *key, dbus_bool_t is_removed, + dbus_bool_t is_added) +{ + MafwGstRenderer *renderer; + gboolean connected; + GValue value = { 0 }; + + g_debug("HAL property modified! jack changed\n"); + connected = _tv_out_is_connected(ctx, udi); + renderer = MAFW_GST_RENDERER(libhal_ctx_get_user_data(ctx)); + if (renderer->tv_connected != connected) { + /* Notify the change */ + renderer->tv_connected = connected; + g_value_init(&value, G_TYPE_BOOLEAN); + g_value_set_boolean(&value, renderer->tv_connected); + mafw_extension_emit_property_changed( + MAFW_EXTENSION(renderer), + MAFW_PROPERTY_GST_RENDERER_TV_CONNECTED, + &value); + g_value_unset(&value); + } + blanking_control(connected == FALSE); +} + +/*---------------------------------------------------------------------------- + GConf notifications + ----------------------------------------------------------------------------*/ + +static void _battery_cover_open_cb(GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + MafwGstRenderer *renderer) +{ + GConfValue *value = NULL; + gboolean is_cover_open; + + value = gconf_entry_get_value(entry); + is_cover_open = gconf_value_get_bool(value); + + if (is_cover_open) { + /* External mmc could be removed!. */ + const gchar *emmc_path = g_getenv("MMC_MOUNTPOINT"); + + mafw_gst_renderer_state_handle_pre_unmount( + MAFW_GST_RENDERER_STATE( + renderer->states[renderer->current_state]), + emmc_path); + } +} + +/*---------------------------------------------------------------------------- + Gnome VFS notifications + ----------------------------------------------------------------------------*/ + +static void _volume_pre_unmount_cb(GnomeVFSVolumeMonitor *monitor, + GnomeVFSVolume *volume, + MafwGstRenderer *renderer) +{ + gchar *location = gnome_vfs_volume_get_activation_uri(volume); + if (!location) { + return; + } + + mafw_gst_renderer_state_handle_pre_unmount( + MAFW_GST_RENDERER_STATE( + renderer->states[renderer->current_state]), + location); + + g_free(location); +} + +/*---------------------------------------------------------------------------- + Signals + ----------------------------------------------------------------------------*/ + + +/** + * _signal_state_changed: + * @self: A #MafwGstRenderer + * + * Signals state_changed to all UIs + **/ +static void _signal_state_changed(MafwGstRenderer * self) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_signal_emit_by_name(MAFW_RENDERER(self), + "state-changed", self->current_state); +} + +/** + * _signal_playlist_changed: + * @self: A #MafwGstRenderer + * + * Signals playlist update to all UIs + **/ +static void _signal_playlist_changed(MafwGstRenderer * self) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_signal_emit_by_name(MAFW_RENDERER(self), + "playlist-changed", self->playlist); +} + +/** + * _signal_media_changed: + * @self: A #MafwGstRenderer + * + * Signals media_changed to all UIs + **/ +static void _signal_media_changed(MafwGstRenderer *self) +{ + + MafwGstRendererPlaybackMode mode; + gint index; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + mode = mafw_gst_renderer_get_playback_mode(MAFW_GST_RENDERER(self)); + if ((mode == MAFW_GST_RENDERER_MODE_STANDALONE) || + (self->iterator == NULL)) { + index = -1; + } else { + index = mafw_playlist_iterator_get_current_index(self->iterator); + } + + g_signal_emit_by_name(MAFW_RENDERER(self), + "media-changed", + index, + self->media->object_id); +} + +/** + * _signal_transport_actions_property_changed: + * @self: A #MafwGstRenderer + * + * Signals transport_actions property_changed to all UIs + **/ +static void _signal_transport_actions_property_changed(MafwGstRenderer * self) +{ + GValue *value; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + value = mafw_gst_renderer_state_get_property_value( + MAFW_GST_RENDERER_STATE( + self->states[self->current_state]), + MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS); + + if (value) { + mafw_extension_emit_property_changed( + MAFW_EXTENSION(self), + MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS, + value); + g_value_unset(value); + g_free(value); + } +} + + +/*---------------------------------------------------------------------------- + State pattern support + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_set_state(MafwGstRenderer *self, MafwPlayState state) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + self->current_state = state; + _signal_state_changed(self); + _signal_transport_actions_property_changed(self); +} + +void mafw_gst_renderer_play(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_play( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_play_object(MafwRenderer *self, + const gchar *object_id, + MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(object_id != NULL); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_play_object( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + object_id, + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_stop(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + renderer->play_failed_count = 0; + mafw_gst_renderer_state_stop( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + + +void mafw_gst_renderer_pause(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_pause( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_resume(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_resume( + MAFW_GST_RENDERER_STATE (renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_next(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + renderer->play_failed_count = 0; + mafw_gst_renderer_state_next( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_previous(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + renderer->play_failed_count = 0; + mafw_gst_renderer_state_previous( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_goto_index(MafwRenderer *self, guint index, + MafwRendererPlaybackCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + renderer->play_failed_count = 0; + mafw_gst_renderer_state_goto_index( + MAFW_GST_RENDERER_STATE(renderer->states[renderer->current_state]), + index, + &error); + + if (callback != NULL) + callback(self, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_get_position(MafwRenderer *self, MafwRendererPositionCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer; + gint pos; + GError *error = NULL; + + g_return_if_fail(callback != NULL); + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + renderer = MAFW_GST_RENDERER(self); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_get_position( + MAFW_GST_RENDERER_STATE (renderer->states[renderer->current_state]), + &pos, + &error); + + callback(self, pos, user_data, error); + if (error) + g_error_free(error); +} + +void mafw_gst_renderer_set_position(MafwRenderer *self, MafwRendererSeekMode mode, + gint seconds, MafwRendererPositionCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) self; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_set_position( + MAFW_GST_RENDERER_STATE (renderer->states[renderer->current_state]), + mode, + seconds, + &error); + + if (callback != NULL) + callback(self, seconds, user_data, error); + if (error) + g_error_free(error); +} + +gboolean mafw_gst_renderer_manage_error_idle(gpointer data) +{ + MafwGstRendererErrorClosure *mec = (MafwGstRendererErrorClosure *) data; + + mafw_gst_renderer_manage_error(mec->renderer, mec->error); + if (mec->error) + g_error_free(mec->error); + g_free(mec); + + return FALSE; +} + +static void _run_error_policy(MafwGstRenderer *self, const GError *in_err, + GError **out_err) +{ + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + gboolean play_next = FALSE; + + /* Check what to do on error */ + if (in_err->code == MAFW_EXTENSION_ERROR_OUT_OF_MEMORY) { + play_next = FALSE; + } else { + MafwGstRendererPlaybackMode mode; + + mode = mafw_gst_renderer_get_playback_mode(self); + + if (mode == MAFW_GST_RENDERER_MODE_PLAYLIST) { + /* In playlist mode we try to play next if + error policy suggests so */ + play_next = + (_get_error_policy(self) == + MAFW_RENDERER_ERROR_POLICY_CONTINUE); + } else { + /* In standalone mode, then switch back to playlist + mode and resume if necessary or move to Stopped + otherwise */ + mafw_gst_renderer_set_playback_mode( + self, MAFW_GST_RENDERER_MODE_PLAYLIST); + mafw_gst_renderer_set_media_playlist(self); + if (self->resume_playlist) { + mafw_gst_renderer_play(MAFW_RENDERER(self), + NULL, NULL); + } else { + mafw_gst_renderer_worker_stop(self->worker); + mafw_gst_renderer_set_state(self, Stopped); + } + if (out_err) *out_err = g_error_copy(in_err); + + /* Bail out, he have already managed the error + for the case of standalone mode */ + return; + } + } + + if (play_next) { + if (self->playlist){ + MafwPlaylistIteratorMovementResult result; + + result = mafw_playlist_iterator_move_to_next(self->iterator, + NULL); + self->play_failed_count++; + + if (mafw_playlist_iterator_get_size(self->iterator, + NULL) <= + self->play_failed_count) + { + mafw_gst_renderer_state_stop( + MAFW_GST_RENDERER_STATE(self->states[self->current_state]), + NULL); + self->play_failed_count = 0; + mafw_gst_renderer_set_media_playlist(self); + } else if (result != + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_OK) { + mafw_playlist_iterator_reset(self->iterator, NULL); + mafw_gst_renderer_set_media_playlist(self); + mafw_gst_renderer_stop(MAFW_RENDERER(self), NULL, NULL); + } else { + mafw_gst_renderer_set_media_playlist(self); + mafw_gst_renderer_play(MAFW_RENDERER(self), NULL, NULL); + } + + if (out_err) *out_err = g_error_copy(in_err); + } + } else { + /* We cannot move to next in the playlist or decided + we do not want to do it, just stop on error */ + mafw_gst_renderer_stop(MAFW_RENDERER(self), NULL, NULL); + if (out_err) *out_err = g_error_copy(in_err); + } +} + +static void _metadata_set_cb(MafwSource *self, const gchar *object_id, + const gchar **failed_keys, gpointer user_data, + const GError *error) +{ + if (error != NULL) { + g_debug("Ignoring error received when setting metadata: " + "%s (%d): %s", g_quark_to_string(error->domain), + error->code, error->message); + } else { + g_debug("Metadata set correctly"); + } +} + +/** + * _update_playcount_metadata_cb: + * @cb_source: The #MafwSource that sent the metadata results + * @cb_object_id: The object ID, whose metadata results were received + * @cb_metadata: GHashTable containing metadata key-value pairs + * @cb_user_data: Optional user data pointer (self) + * @cb_error: Set if any errors occurred during metadata browsing + * + * Receives the results of a metadata request about the playcount. It increases + * it, or sets to 1, and sets the metadata to that. + */ +static void _update_playcount_metadata_cb (MafwSource *cb_source, + const gchar *cb_object_id, + GHashTable *cb_metadata, + gpointer cb_user_data, + const GError *cb_error) +{ + GValue *curval = NULL; + gint curplaycount = -1; + GHashTable *mdata = cb_user_data; + + if (cb_error == NULL) { + if (cb_metadata) + curval = mafw_metadata_first(cb_metadata, + MAFW_METADATA_KEY_PLAY_COUNT); + if (curval && !G_VALUE_HOLDS(curval, G_TYPE_INT)) + goto set_data; + if (curval) + { + curplaycount = g_value_get_int(curval); + curplaycount++; + } + else + { /* Playing at first time, or not supported... */ + curplaycount = 1; + } + if (!mdata) + mdata = mafw_metadata_new(); + mafw_metadata_add_int(mdata, + MAFW_METADATA_KEY_PLAY_COUNT, + curplaycount); + + } else { + g_warning("_playcount_metadata received an error: " + "%s (%d): %s", g_quark_to_string(cb_error->domain), + cb_error->code, cb_error->message); + if (mdata) + g_hash_table_unref(mdata); + return; + } +set_data: + + if (mdata) + { + mafw_source_set_metadata(cb_source, cb_object_id, mdata, + _metadata_set_cb, NULL); + g_hash_table_unref(mdata); + } +} + +/** + * mafw_gst_renderer_add_lastplayed: + * @mdata: Exisiting mdata, or NULL + * + * Sets the MAFW_METADATA_KEY_LAST_PLAYED metadata in the given metadata-table, + * or creates a new metadata-table, and sets the current time there. + */ +static GHashTable *mafw_gst_renderer_add_lastplayed(GHashTable *mdata) +{ + GHashTable *metadata; + GTimeVal timeval; + + + if (!mdata) + metadata = mafw_metadata_new(); + else + metadata = mdata; + + + + g_get_current_time(&timeval); + + mafw_metadata_add_long(metadata, + MAFW_METADATA_KEY_LAST_PLAYED, + timeval.tv_sec); + return metadata; +} + +/** + * mafw_gst_renderer_increase_playcount: + * @self: Gst renderer + * @object_id: The object ID of the touched object + * @mdat: Existing metadatas to add the playcount to, or NULL + * + * Increases the playcount of the given object. + */ +static void mafw_gst_renderer_increase_playcount(MafwGstRenderer* self, + const gchar *object_id, GHashTable *mdat) +{ + MafwSource* source; + + g_assert(self != NULL); + source = _get_source(self, object_id); + if (source != NULL) + { + static const gchar * const keys[] = + { MAFW_METADATA_KEY_PLAY_COUNT, NULL }; + + mafw_source_get_metadata(source, object_id, + keys, + _update_playcount_metadata_cb, + mdat); + + } +} + +/** + * mafw_gst_renderer_update_stats: + * @data: user data + * + * Updates both playcount and lastplayed after a while. + **/ +gboolean mafw_gst_renderer_update_stats(gpointer data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer *) data; + + /* Update stats only for audio content */ + if (renderer->media->object_id && + !renderer->worker->media.has_visual_content) { + GHashTable *mdata = mafw_gst_renderer_add_lastplayed(NULL); + mafw_gst_renderer_increase_playcount(renderer, + renderer->media->object_id, + mdata); + } + renderer->update_playcount_id = 0; + return FALSE; +} + +void mafw_gst_renderer_update_source_duration(MafwGstRenderer *renderer, + gint duration) +{ + GHashTable *metadata; + MafwSource* source; + + source = _get_source(renderer, renderer->media->object_id); + g_return_if_fail(source != NULL); + + renderer->media->duration = duration; + + g_debug("updated source duration to %d", duration); + + metadata = mafw_metadata_new(); + mafw_metadata_add_int(metadata, MAFW_METADATA_KEY_DURATION, duration); + + mafw_source_set_metadata(source, renderer->media->object_id, metadata, + _metadata_set_cb, NULL); + g_hash_table_unref(metadata); +} + +/** + * _notify_metadata: + * @source: The #MafwSource that sent the metadata results + * @objectid: The object ID, whose metadata results were received + * @metadata: GHashTable containing metadata key-value pairs + * @userdata: Optional user data pointer (self) + * @error: Set if any errors occurred during metadata browsing + * + * Receives the results of a metadata request. + */ +static void _notify_metadata (MafwSource *cb_source, + const gchar *cb_object_id, + GHashTable *cb_metadata, + gpointer cb_user_data, + const GError *cb_error) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) cb_user_data; + GError *mafw_error = NULL; + GError *error = NULL; + GValue *mval; + + g_return_if_fail(MAFW_IS_GST_RENDERER(renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + g_debug("running _notify_metadata..."); + + mval = mafw_metadata_first(cb_metadata, MAFW_METADATA_KEY_URI); + + if (cb_error == NULL && mval != NULL) { + mafw_gst_renderer_state_notify_metadata( + MAFW_GST_RENDERER_STATE( + renderer->states[renderer->current_state]), + cb_object_id, + cb_metadata, + &error); + } + else { + g_set_error(&mafw_error, + MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_URI_NOT_AVAILABLE, "%s", + cb_error ? cb_error->message : "URI not available"); + mafw_gst_renderer_manage_error(renderer, mafw_error); + g_error_free(mafw_error); + } +} + +static void _notify_play(MafwGstRendererWorker *worker, gpointer owner) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) owner; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + g_debug("running _notify_play..."); + + mafw_gst_renderer_state_notify_play(renderer->states[renderer->current_state], + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION (renderer), "error", + error->domain, + error->code, + error->message); + g_error_free (error); + } +} + +static void _notify_pause(MafwGstRendererWorker *worker, gpointer owner) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) owner; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER (renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_notify_pause(renderer->states[renderer->current_state], + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION (renderer), "error", + error->domain, error->code, + error->message); + g_error_free(error); + } +} + +static void _notify_buffer_status (MafwGstRendererWorker *worker, + gpointer owner, + gdouble percent) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) owner; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_notify_buffer_status( + renderer->states[renderer->current_state], + percent, + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION (renderer), "error", + error->domain, error->code, + error->message); + g_error_free(error); + } +} + +static void _notify_seek(MafwGstRendererWorker *worker, gpointer owner) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) owner; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_notify_seek(renderer->states[renderer->current_state], + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION(renderer), "error", + error->domain, error->code, + error->message); + g_error_free(error); + } +} + +static void _playlist_changed_handler(MafwPlaylistIterator *iterator, + gboolean clip_changed, GQuark domain, + gint code, const gchar *message, + gpointer user_data) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) user_data; + + g_return_if_fail(MAFW_IS_GST_RENDERER(renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + /* We update the current index and media here, for this is + the same for all the states. Then we delegate in the state + to finish the task (for example, start playback if needed) */ + + if (renderer->playlist == NULL) { + g_critical("Got iterator:contents-changed but renderer has no" \ + "playlist assigned!. Skipping..."); + return; + } + + if (domain != 0) { + g_signal_emit_by_name(MAFW_EXTENSION(renderer), "error", + domain, code, message); + } else { + GError *error = NULL; + MafwGstRendererPlaybackMode mode; + + mode = mafw_gst_renderer_get_playback_mode(renderer); + + /* Only in non-playobject mode */ + if (clip_changed && mode == MAFW_GST_RENDERER_MODE_PLAYLIST) + mafw_gst_renderer_set_media_playlist(renderer); + + /* We let the state know if the current clip has changed as + result of this operation, so it can do its work */ + mafw_gst_renderer_state_playlist_contents_changed_handler( + renderer->states[renderer->current_state], + clip_changed, + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION(renderer), "error", + error->domain, error->code, + error->message); + g_error_free(error); + } + } +} + +static void _error_handler(MafwGstRendererWorker *worker, gpointer owner, + const GError *error) +{ + MafwGstRenderer *renderer = MAFW_GST_RENDERER(owner); + + mafw_gst_renderer_manage_error(renderer, error); +} + +void mafw_gst_renderer_manage_error(MafwGstRenderer *self, const GError *error) +{ + GError *new_err = NULL; + GError *raise_error = NULL; + GQuark new_err_domain = MAFW_RENDERER_ERROR; + gint new_err_code = 0; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + + g_return_if_fail((self->states != 0) && + (self->current_state != _LastMafwPlayState) && + (self->states[self->current_state] != NULL)); + + g_warning("Got error in renderer:\n\tdomain: %d, code: %d, message: %s", + error->domain, error->code, error->message); + + /* Get a MAFW error */ + if (error->domain == GST_RESOURCE_ERROR) { + /* handle RESOURCE errors */ + switch (error->code) { + case GST_RESOURCE_ERROR_READ: + if (is_current_uri_stream(self)) { +#ifdef HAVE_CONIC + if (self->connected) { + new_err_code = MAFW_RENDERER_ERROR_STREAM_DISCONNECTED; + } else { + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_NETWORK_DOWN; + } +#else + /* Stream + cannot read resource -> + disconnected */ + new_err_code = MAFW_RENDERER_ERROR_STREAM_DISCONNECTED; +#endif + } else { + /* This shouldn't happen */ + /* Unknown RESOURCE error */ + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_FAILED; + } + break; + case GST_RESOURCE_ERROR_NOT_FOUND: +#ifdef HAVE_CONIC + if (!is_current_uri_stream(self) || self->connected) { + new_err_code = + MAFW_RENDERER_ERROR_INVALID_URI; + } else { + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_NETWORK_DOWN; + } +#else + new_err_code = + MAFW_RENDERER_ERROR_INVALID_URI; +#endif + break; + case GST_RESOURCE_ERROR_OPEN_READ_WRITE: + case GST_RESOURCE_ERROR_OPEN_READ: +#ifdef HAVE_CONIC + if (!is_current_uri_stream(self) || self->connected) { + new_err_code = + MAFW_RENDERER_ERROR_MEDIA_NOT_FOUND; + } else { + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_NETWORK_DOWN; + } +#else + new_err_code = + MAFW_RENDERER_ERROR_MEDIA_NOT_FOUND; +#endif + break; + case GST_RESOURCE_ERROR_NO_SPACE_LEFT: + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_OUT_OF_MEMORY; + break; + case GST_RESOURCE_ERROR_WRITE: + /* DSP renderers send ERROR_WRITE when they find + corrupted data */ + new_err_code = MAFW_RENDERER_ERROR_CORRUPTED_FILE; + break; + case GST_RESOURCE_ERROR_SEEK: + new_err_code = MAFW_RENDERER_ERROR_CANNOT_SET_POSITION; + break; + default: + /* Unknown RESOURCE error */ + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_FAILED; + } + + } else if (error->domain == GST_STREAM_ERROR) { + /* handle STREAM errors */ + switch (error->code) { + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + new_err_code = MAFW_RENDERER_ERROR_TYPE_NOT_AVAILABLE; + break; + case GST_STREAM_ERROR_FORMAT: + case GST_STREAM_ERROR_WRONG_TYPE: + case GST_STREAM_ERROR_FAILED: + new_err_code = MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE; + break; + case GST_STREAM_ERROR_DECODE: + case GST_STREAM_ERROR_DEMUX: + new_err_code = MAFW_RENDERER_ERROR_CORRUPTED_FILE; + break; + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + new_err_code = MAFW_RENDERER_ERROR_CODEC_NOT_FOUND; + break; + case GST_STREAM_ERROR_DECRYPT: + case GST_STREAM_ERROR_DECRYPT_NOKEY: + new_err_code = MAFW_RENDERER_ERROR_DRM; + break; + default: + /* Unknown STREAM error */ + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_FAILED; + } + } else if (error->domain == MAFW_GST_RENDERER_ERROR) { + /* Handle own errors. Errors that belong to this domain: + - MAFW_GST_RENDERER_ERROR_PLUGIN_NOT_FOUND, + - MAFW_GST_RENDERER_ERROR_VIDEO_CODEC_NOT_SUPPORTED, + - MAFW_GST_RENDERER_ERROR_AUDIO_CODEC_NOT_SUPPORTED */ + new_err_code = MAFW_RENDERER_ERROR_UNSUPPORTED_TYPE; + } else if (error->domain == MAFW_RENDERER_ERROR) { + /* Worker may have sent MAFW_RENDERER_ERROR as well. + No processing needed */ + new_err_code = error->code; + } else { + /* default */ + /* Unknown error */ + new_err_domain = MAFW_EXTENSION_ERROR; + new_err_code = MAFW_EXTENSION_ERROR_FAILED; + } + + g_set_error(&new_err, new_err_domain, new_err_code, "%s", error->message); + + _run_error_policy(self, new_err, &raise_error); + g_error_free(new_err); + + if (raise_error) { + g_signal_emit_by_name(MAFW_EXTENSION (self), "error", + raise_error->domain, + raise_error->code, + raise_error->message); + g_error_free(raise_error); + } +} + +static void _notify_eos(MafwGstRendererWorker *worker, gpointer owner) +{ + MafwGstRenderer *renderer = (MafwGstRenderer*) owner; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER (renderer)); + + g_return_if_fail((renderer->states != 0) && + (renderer->current_state != _LastMafwPlayState) && + (renderer->states[renderer->current_state] != NULL)); + + mafw_gst_renderer_state_notify_eos(renderer->states[renderer->current_state], + &error); + + if (error != NULL) { + g_signal_emit_by_name(MAFW_EXTENSION(renderer), "error", + error->domain, error->code, + error->message); + g_error_free(error); + } +} + +/*---------------------------------------------------------------------------- + Status + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_get_status(MafwRenderer *self, MafwRendererStatusCB callback, + gpointer user_data) +{ + MafwGstRenderer* renderer; + gint index; + MafwGstRendererPlaybackMode mode; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(callback != NULL); + renderer = MAFW_GST_RENDERER(self); + + mode = mafw_gst_renderer_get_playback_mode(MAFW_GST_RENDERER(self)); + if ((mode == MAFW_GST_RENDERER_MODE_STANDALONE) || (renderer->iterator == NULL)) { + index = -1; + } else { + index = + mafw_playlist_iterator_get_current_index(renderer->iterator); + } + + /* TODO: Set error parameter */ + callback(self, renderer->playlist, index, renderer->current_state, + (const gchar*) renderer->media->object_id, user_data, NULL); +} + +void mafw_gst_renderer_get_current_metadata(MafwRenderer *self, + MafwRendererMetadataResultCB callback, + gpointer user_data) +{ + MafwGstRenderer *renderer; + GHashTable *metadata; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + renderer = MAFW_GST_RENDERER(self); + + metadata = mafw_gst_renderer_worker_get_current_metadata( + renderer->worker); + + callback(self, + (const gchar*) renderer->media->object_id, + metadata, + user_data, + NULL); +} + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +static void +_playlist_contents_changed_handler(MafwPlaylist *playlist, + guint from, guint nremove, + guint nreplace, + MafwGstRenderer *renderer) +{ + /* Item(s) added to playlist, so new playable items could come */ + if (nreplace) + renderer->play_failed_count = 0; +} + +gboolean mafw_gst_renderer_assign_playlist(MafwRenderer *self, + MafwPlaylist *playlist, + GError **error) +{ + MafwGstRenderer* renderer = (MafwGstRenderer*) self; + + g_return_val_if_fail(MAFW_IS_GST_RENDERER(self), FALSE); + + /* Get rid of previously assigned playlist */ + if (renderer->playlist != NULL) { + g_signal_handlers_disconnect_matched(renderer->iterator, + (GSignalMatchType) G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, + _playlist_changed_handler, + NULL); + g_signal_handlers_disconnect_matched(renderer->playlist, + (GSignalMatchType) G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, + G_CALLBACK(_playlist_contents_changed_handler), + NULL); + /* Decrement the use count of the previous playlist because the + renderer isn't going to use it more */ + mafw_playlist_decrement_use_count(renderer->playlist, NULL); + + g_object_unref(renderer->iterator); + g_object_unref(renderer->playlist); + } + + /* Assign the new playlist */ + if (playlist == NULL) { + renderer->playlist = NULL; + renderer->iterator = NULL; + } else { + GError *new_error = NULL; + MafwPlaylistIterator *iterator = NULL; + + iterator = mafw_playlist_iterator_new(); + mafw_playlist_iterator_initialize(iterator, playlist, + &new_error); + + g_object_ref(playlist); + + if (new_error == NULL) { + + renderer->playlist = playlist; + renderer->iterator = iterator; + + /* Increment the use_count to avoid the playlist destruction + while the playlist is assigned to some renderer */ + mafw_playlist_increment_use_count(renderer->playlist, NULL); + + g_signal_connect(iterator, + "playlist-changed", + G_CALLBACK(_playlist_changed_handler), + renderer); + g_signal_connect(renderer->playlist, + "contents-changed", + G_CALLBACK(_playlist_contents_changed_handler), + renderer); + } + else { + g_propagate_error (error, new_error); + } + } + + /* Set the new media and signal playlist changed signal */ + _signal_playlist_changed(renderer); + mafw_gst_renderer_set_media_playlist(renderer); + + + /* Stop playback */ + mafw_gst_renderer_stop(MAFW_RENDERER(renderer), NULL , NULL); + + return TRUE; +} + +MafwGstRendererMovementResult mafw_gst_renderer_move(MafwGstRenderer *renderer, + MafwGstRendererMovementType type, + guint index, + GError **error) +{ + MafwGstRendererMovementResult value = MAFW_GST_RENDERER_MOVE_RESULT_OK; + + if (renderer->playlist == NULL) { + value = MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST; + } else { + MafwPlaylistIteratorMovementResult result; + + switch (type) { + case MAFW_GST_RENDERER_MOVE_TYPE_INDEX: + result = + mafw_playlist_iterator_move_to_index(renderer->iterator, + index, + error); + break; + case MAFW_GST_RENDERER_MOVE_TYPE_PREV: + result = + mafw_playlist_iterator_move_to_prev(renderer->iterator, + error); + break; + case MAFW_GST_RENDERER_MOVE_TYPE_NEXT: + result = + mafw_playlist_iterator_move_to_next(renderer->iterator, + error); + break; + } + + switch (result) { + case MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_OK: + value = MAFW_GST_RENDERER_MOVE_RESULT_OK; + mafw_gst_renderer_set_media_playlist(renderer); + break; + case MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_INVALID: + g_critical("Iterator is invalid!"); + value = MAFW_GST_RENDERER_MOVE_RESULT_ERROR; + break; + case MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_ERROR: + value = MAFW_GST_RENDERER_MOVE_RESULT_ERROR; + break; + case MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_LIMIT: + value = MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT; + break; + } + } + + return value; +} + +/*---------------------------------------------------------------------------- + Properties + ----------------------------------------------------------------------------*/ + +static void _set_error_policy(MafwGstRenderer *renderer, MafwRendererErrorPolicy policy) +{ + renderer->error_policy = policy; +} + +static MafwRendererErrorPolicy _get_error_policy(MafwGstRenderer *renderer) +{ + return renderer->error_policy; +} + +static void mafw_gst_renderer_get_property(MafwExtension *self, + const gchar *key, + MafwExtensionPropertyCallback callback, + gpointer user_data) +{ + MafwGstRenderer *renderer; + GValue *value = NULL; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(callback != NULL); + g_return_if_fail(key != NULL); + + renderer = MAFW_GST_RENDERER(self); + if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) { + guint volume; + + volume = mafw_gst_renderer_worker_get_volume( + renderer->worker); + + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UINT); + g_value_set_uint(value, volume); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) { + gboolean mute; + mute = mafw_gst_renderer_worker_get_mute(renderer->worker); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_BOOLEAN); + g_value_set_boolean(value, mute); + } + else if (!strcmp (key, MAFW_PROPERTY_RENDERER_XID)) { + guint xid; + xid = mafw_gst_renderer_worker_get_xid(renderer->worker); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UINT); + g_value_set_uint(value, xid); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_ERROR_POLICY)) { + guint policy; + policy = _get_error_policy(renderer); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_UINT); + g_value_set_uint(value, policy); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_AUTOPAINT)) { + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_BOOLEAN); + g_value_set_boolean( + value, + mafw_gst_renderer_worker_get_autopaint( + renderer->worker)); + } else if (!strcmp(key, MAFW_PROPERTY_RENDERER_COLORKEY)) { + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_INT); + g_value_set_int( + value, + mafw_gst_renderer_worker_get_colorkey( + renderer->worker)); + } +#ifdef HAVE_GDKPIXBUF + else if (!strcmp(key, + MAFW_PROPERTY_GST_RENDERER_CURRENT_FRAME_ON_PAUSE)) { + gboolean current_frame_on_pause; + current_frame_on_pause = + mafw_gst_renderer_worker_get_current_frame_on_pause(renderer->worker); + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_BOOLEAN); + g_value_set_boolean(value, current_frame_on_pause); + } +#endif + else if (!strcmp(key, + MAFW_PROPERTY_GST_RENDERER_TV_CONNECTED)) { + value = g_new0(GValue, 1); + g_value_init(value, G_TYPE_BOOLEAN); + g_value_set_boolean(value, renderer->tv_connected); + } + else if (!strcmp(key, + MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS)){ + /* Delegate in the state. */ + value = mafw_gst_renderer_state_get_property_value( + MAFW_GST_RENDERER_STATE( + renderer->states[renderer->current_state]), + MAFW_PROPERTY_RENDERER_TRANSPORT_ACTIONS); + + if (!value) { + /* Something goes wrong. */ + error = g_error_new( + MAFW_GST_RENDERER_ERROR, + MAFW_EXTENSION_ERROR_GET_PROPERTY, + "Error while getting the property value"); + } + } + else { + /* Unsupported property */ + error = g_error_new(MAFW_GST_RENDERER_ERROR, + MAFW_EXTENSION_ERROR_GET_PROPERTY, + "Unsupported property"); + } + + callback(self, key, value, user_data, error); +} + +static void mafw_gst_renderer_set_property(MafwExtension *self, + const gchar *key, + const GValue *value) +{ + MafwGstRenderer *renderer; + + g_return_if_fail(MAFW_IS_GST_RENDERER(self)); + g_return_if_fail(key != NULL); + + renderer = MAFW_GST_RENDERER(self); + + if (!strcmp(key, MAFW_PROPERTY_RENDERER_VOLUME)) { + guint volume = g_value_get_uint(value); + if (volume > 100) + volume = 100; + mafw_gst_renderer_worker_set_volume(renderer->worker, + volume); + /* Property-changed emision is done by worker */ + return; + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_MUTE)) { + gboolean mute = g_value_get_boolean(value); + mafw_gst_renderer_worker_set_mute(renderer->worker, mute); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_XID)) { + XID xid = g_value_get_uint(value); + mafw_gst_renderer_worker_set_xid(renderer->worker, xid); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_ERROR_POLICY)) { + MafwRendererErrorPolicy policy = g_value_get_uint(value); + _set_error_policy(renderer, policy); + } + else if (!strcmp(key, MAFW_PROPERTY_RENDERER_AUTOPAINT)) { + mafw_gst_renderer_worker_set_autopaint( + renderer->worker, + g_value_get_boolean(value)); + } +#ifdef HAVE_GDKPIXBUF + else if (!strcmp(key, + MAFW_PROPERTY_GST_RENDERER_CURRENT_FRAME_ON_PAUSE)) { + gboolean current_frame_on_pause = g_value_get_boolean(value); + mafw_gst_renderer_worker_set_current_frame_on_pause(renderer->worker, + current_frame_on_pause); + } +#endif + else return; + + /* FIXME I'm not sure when to emit property-changed signals. + * Maybe we should let the worker do it, when the change + * reached the hardware... */ + mafw_extension_emit_property_changed(self, key, value); +} + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-gst-renderer.h b/libmafw-gst-renderer/mafw-gst-renderer.h new file mode 100644 index 0000000..0350e34 --- /dev/null +++ b/libmafw-gst-renderer/mafw-gst-renderer.h @@ -0,0 +1,293 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef MAFW_GST_RENDERER_H +#define MAFW_GST_RENDERER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "mafw-gst-renderer-utils.h" +#include "mafw-gst-renderer-worker.h" +#include "mafw-playlist-iterator.h" +/* Solving the cyclic dependencies */ +typedef struct _MafwGstRenderer MafwGstRenderer; +typedef struct _MafwGstRendererClass MafwGstRendererClass; +#include "mafw-gst-renderer-state.h" + +#ifdef HAVE_CONIC +#include +#endif + +typedef enum { + MAFW_GST_RENDERER_ERROR_PLUGIN_NOT_FOUND, + MAFW_GST_RENDERER_ERROR_VIDEO_CODEC_NOT_SUPPORTED, + MAFW_GST_RENDERER_ERROR_AUDIO_CODEC_NOT_SUPPORTED, +} MafwGstRendererError; + +typedef enum { + MAFW_GST_RENDERER_MODE_PLAYLIST, + MAFW_GST_RENDERER_MODE_STANDALONE, +} MafwGstRendererPlaybackMode; + +typedef enum { + MAFW_GST_RENDERER_MOVE_RESULT_OK, + MAFW_GST_RENDERER_MOVE_RESULT_NO_PLAYLIST, + MAFW_GST_RENDERER_MOVE_RESULT_PLAYLIST_LIMIT, + MAFW_GST_RENDERER_MOVE_RESULT_ERROR, +} MafwGstRendererMovementResult; + +typedef enum { + MAFW_GST_RENDERER_MOVE_TYPE_INDEX, + MAFW_GST_RENDERER_MOVE_TYPE_PREV, + MAFW_GST_RENDERER_MOVE_TYPE_NEXT, +} MafwGstRendererMovementType; + +#ifdef HAVE_GDKPIXBUF +#define MAFW_PROPERTY_GST_RENDERER_CURRENT_FRAME_ON_PAUSE \ + "current-frame-on-pause" +#endif + +#define MAFW_PROPERTY_GST_RENDERER_TV_CONNECTED "tv-connected" + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_GST_RENDERER \ + (mafw_gst_renderer_get_type()) +#define MAFW_GST_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_GST_RENDERER, MafwGstRenderer)) +#define MAFW_IS_GST_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_GST_RENDERER)) +#define MAFW_GST_RENDERER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_GST_RENDERER, MafwGstRenderer)) +#define MAFW_GST_RENDERER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_GST_RENDERER, \ + MafwGstRendererClass)) +#define MAFW_IS_GST_RENDERER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_GST_RENDERER)) + +#define MAFW_GST_RENDERER_ERROR (mafw_gst_renderer_error_quark ()) + +/* Gst renderer plugin name for the plugin descriptor */ +#define MAFW_GST_RENDERER_PLUGIN_NAME "Mafw-Gst-Renderer-Plugin" +/* Gst renderer name */ +#define MAFW_GST_RENDERER_NAME "Mafw-Gst-Renderer" +/* Gst renderer UUID */ +#define MAFW_GST_RENDERER_UUID "gstrenderer" + +/*---------------------------------------------------------------------------- + Type definitions + ----------------------------------------------------------------------------*/ + +typedef struct { + gchar *object_id; + gchar *uri; + gchar *title; + gchar *artist; + gchar *album; + + gint duration; + gint position; + + /* Seekability coming from source */ + SeekabilityType seekability; +} MafwGstRendererMedia; + +struct _MafwGstRendererClass { + MafwRendererClass parent; +}; + +/* + * media: Current media details + * worker: Worker + * registry: The registry that owns this renderer + * media_timer: Stream timer data + * current_state: The renderer's current state + * playlist: The renderer's playlist + * play_index: A playlist index that is currently playing + * seek_pending: Seek is pending or ongoing + * seek_type_pending: Type of the pending seek + * seeking_to: The position of pending seek (milliseconds) + * is_stream: is the URI a stream? + * play_failed_count: The number of unably played items from the playlist. + * playback_mode: Playback mode + * resume_playlist: Do we want to resume playlist playback when play_object + * is finished + * states: State array + * error_policy: error policy + * tv_connected: if TV-out cable is connected + */ +struct _MafwGstRenderer{ + MafwRenderer parent; + + MafwGstRendererMedia *media; + MafwGstRendererWorker *worker; + MafwRegistry *registry; + LibHalContext *hal_ctx; + MafwPlayState current_state; + MafwPlaylist *playlist; + MafwPlaylistIterator *iterator; + gboolean seek_pending; + GstSeekType seek_type_pending; + gint seeking_to; + gboolean is_stream; + gint update_playcount_id; + guint play_failed_count; + + MafwGstRendererPlaybackMode playback_mode; + gboolean resume_playlist; + MafwGstRendererState **states; + MafwRendererErrorPolicy error_policy; + gboolean tv_connected; + +#ifdef HAVE_CONIC + gboolean connected; + ConIcConnection *connection; +#endif + GConfClient *gconf_client; +}; + +typedef struct { + MafwGstRenderer *renderer; + GError *error; +} MafwGstRendererErrorClosure; + +G_BEGIN_DECLS + +GType mafw_gst_renderer_get_type(void); +GObject *mafw_gst_renderer_new(MafwRegistry *registry); +GQuark mafw_gst_renderer_error_quark(void); + +/*---------------------------------------------------------------------------- + Playback + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_play(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_play_object(MafwRenderer *self, const gchar *object_id, + MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_stop(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_pause(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_resume(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); + +/*---------------------------------------------------------------------------- + Status + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_get_status(MafwRenderer *self, MafwRendererStatusCB callback, + gpointer user_data); + +/*---------------------------------------------------------------------------- + Set Media + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_set_object(MafwGstRenderer *self, const gchar *object_id); +void mafw_gst_renderer_clear_media(MafwGstRenderer *self); + +/*---------------------------------------------------------------------------- + Metadata + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_get_metadata(MafwGstRenderer* self, const gchar* objectid, + GError **error); +gboolean mafw_gst_renderer_update_stats(gpointer data); + +/*---------------------------------------------------------------------------- + Playlist + ----------------------------------------------------------------------------*/ + +gboolean mafw_gst_renderer_assign_playlist(MafwRenderer *self, + MafwPlaylist *playlist, + GError **error); +void mafw_gst_renderer_next(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_previous(MafwRenderer *self, MafwRendererPlaybackCB callback, + gpointer user_data); +void mafw_gst_renderer_goto_index(MafwRenderer *self, guint index, + MafwRendererPlaybackCB callback, + gpointer user_data); +MafwGstRendererMovementResult mafw_gst_renderer_move(MafwGstRenderer *renderer, + MafwGstRendererMovementType type, + guint index, + GError **error); + +/*---------------------------------------------------------------------------- + Set media + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_set_media_playlist(MafwGstRenderer* self); + +/*---------------------------------------------------------------------------- + Position + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_set_position(MafwRenderer *self, + MafwRendererSeekMode mode, gint seconds, + MafwRendererPositionCB callback, + gpointer user_data); +void mafw_gst_renderer_get_position(MafwRenderer *self, MafwRendererPositionCB callback, + gpointer user_data); + +/*---------------------------------------------------------------------------- + Metadata + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_get_current_metadata(MafwRenderer *self, + MafwRendererMetadataResultCB callback, + gpointer user_data); + +/*---------------------------------------------------------------------------- + Local API + ----------------------------------------------------------------------------*/ + +void mafw_gst_renderer_set_state(MafwGstRenderer *self, MafwPlayState state); + +gboolean mafw_gst_renderer_manage_error_idle(gpointer data); + +void mafw_gst_renderer_manage_error(MafwGstRenderer *self, const GError *error); + +void mafw_gst_renderer_set_playback_mode(MafwGstRenderer *self, + MafwGstRendererPlaybackMode mode); + +MafwGstRendererPlaybackMode mafw_gst_renderer_get_playback_mode( + MafwGstRenderer *self); + +void mafw_gst_renderer_update_source_duration(MafwGstRenderer *renderer, + gint duration); + +G_END_DECLS + +#endif + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/libmafw-gst-renderer/mafw-playlist-iterator.c b/libmafw-gst-renderer/mafw-playlist-iterator.c new file mode 100644 index 0000000..4a8b869 --- /dev/null +++ b/libmafw-gst-renderer/mafw-playlist-iterator.c @@ -0,0 +1,521 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "mafw-playlist-iterator.h" +#include "mafw-gst-renderer-marshal.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-playlist-iterator" + +struct _MafwPlaylistIteratorPrivate { + MafwPlaylist *playlist; + gint current_index; + gchar *current_objectid; + gint size; +}; + +typedef gboolean (*movement_function) (MafwPlaylist *playlist, + guint *index, + gchar **objectid, + GError **error); + +enum { + PLAYLIST_CHANGED = 0, + LAST_SIGNAL, +}; + +static guint mafw_playlist_iterator_signals[LAST_SIGNAL]; + +G_DEFINE_TYPE(MafwPlaylistIterator, mafw_playlist_iterator, G_TYPE_OBJECT); + +static void +mafw_playlist_iterator_dispose(GObject *object) +{ + MafwPlaylistIterator *iterator = (MafwPlaylistIterator *) object; + + g_return_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator)); + + mafw_playlist_iterator_invalidate(iterator); + + G_OBJECT_CLASS(mafw_playlist_iterator_parent_class)->dispose(object); +} + +static void +mafw_playlist_iterator_class_init(MafwPlaylistIteratorClass *klass) +{ + GObjectClass *gclass = NULL; + + gclass = G_OBJECT_CLASS(klass); + g_return_if_fail(gclass != NULL); + + g_type_class_add_private(klass, sizeof(MafwPlaylistIteratorPrivate)); + + gclass->dispose = mafw_playlist_iterator_dispose; + + mafw_playlist_iterator_signals[PLAYLIST_CHANGED] = + g_signal_new("playlist-changed", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(MafwPlaylistIteratorClass, playlist_changed), + NULL, + NULL, + mafw_gst_renderer_marshal_VOID__BOOLEAN_UINT_INT_STRING, + G_TYPE_NONE, + 4, + G_TYPE_BOOLEAN, + G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING); +} + +static void +mafw_playlist_iterator_init(MafwPlaylistIterator *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, + MAFW_TYPE_PLAYLIST_ITERATOR, + MafwPlaylistIteratorPrivate); +} + +static void +mafw_playlist_iterator_set_data(MafwPlaylistIterator *iterator, gint index, + gchar *objectid) +{ + g_assert(mafw_playlist_iterator_is_valid(iterator)); + + g_free(iterator->priv->current_objectid); + iterator->priv->current_index = index; + iterator->priv->current_objectid = objectid; +} + +static MafwPlaylistIteratorMovementResult +mafw_playlist_iterator_move_to_next_in_direction(MafwPlaylistIterator *iterator, + movement_function get_next_in_direction, + GError **error) +{ + gint index; + gchar *objectid = NULL; + GError *new_error = NULL; + gboolean playlist_movement_result = FALSE; + MafwPlaylistIteratorMovementResult iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_OK; + + g_return_val_if_fail(mafw_playlist_iterator_is_valid(iterator), + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_INVALID); + + index = iterator->priv->current_index; + + playlist_movement_result = + get_next_in_direction (iterator->priv->playlist, + (guint *) &index, + &objectid, &new_error); + + if (new_error != NULL) { + g_propagate_error(error, new_error); + iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_ERROR; + } else if (playlist_movement_result) { + mafw_playlist_iterator_set_data(iterator, index, objectid); + } else { + iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_LIMIT; + } + + return iterator_movement_result; +} + +static void +mafw_playlist_iterator_playlist_contents_changed_handler(MafwPlaylist *playlist, + guint from, + guint nremove, + guint nreplace, + gpointer user_data) +{ + gint play_index; + gboolean clip_changed = FALSE; + GError *error = NULL; + MafwPlaylistIterator *iterator = (MafwPlaylistIterator*) user_data; + + g_return_if_fail(MAFW_IS_PLAYLIST(playlist)); + g_return_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator)); + + if (iterator->priv->playlist == NULL) { + g_critical("Got playlist:contents-changed but renderer has no" \ + "playlist assigned!. Skipping..."); + return; + } + + play_index = iterator->priv->current_index; + iterator->priv->size += nreplace; + + if (nremove > 0) { + /* Items have been removed from the playlist */ + iterator->priv->size -= nremove; + if ((play_index >= from) && + (play_index < from + nremove)) { + /* The current index has been removed */ + guint pls_size = + mafw_playlist_iterator_get_size(iterator, + &error); + if (error == NULL) { + /* Is the current index invalid now? If not, + set current item to the last in the playlist, + otherwise the keep the index and update the + media */ + if (pls_size == 0) { + mafw_playlist_iterator_set_data(iterator, -1, NULL); + } else if (play_index >= pls_size) { + mafw_playlist_iterator_move_to_index(iterator, + pls_size - 1, + &error); + } else { + mafw_playlist_iterator_update(iterator, + &error); + } + + clip_changed = TRUE; + } + } else if (from < play_index) { + /* The current index has been moved towards + the head of the playlist */ + play_index -= nremove; + if (play_index < 0) { + play_index = 0; + } + mafw_playlist_iterator_move_to_index(iterator, + play_index, + &error); + } + } else if (nremove == 0) { + /* Items have been inserted in the playlist */ + if (play_index == -1) { + /* First item has been added to an empty playlist */ + mafw_playlist_iterator_reset(iterator, + &error); + clip_changed = TRUE; + } else if (play_index >= from) { + /* The current item has been moved towards the + tail of the playlist */ + mafw_playlist_iterator_move_to_index(iterator, + play_index + nreplace, + &error); + } + } + + if (error != NULL) { + g_critical("playlist::contents-changed handler failed " + "with \"%s\"", error->message); + g_signal_emit(iterator, + mafw_playlist_iterator_signals[PLAYLIST_CHANGED], + 0, FALSE, error->domain, error->code, error->message); + g_error_free (error); + } else { + g_signal_emit(iterator, + mafw_playlist_iterator_signals[PLAYLIST_CHANGED], + 0, clip_changed, 0, 0, NULL); + } +} + +static void +mafw_playlist_iterator_playlist_item_moved_handler(MafwPlaylist *playlist, + guint from, + guint to, + gpointer user_data) +{ + MafwPlaylistIterator *iterator = (MafwPlaylistIterator *) user_data; + gint play_index; + GError *error = NULL; + + g_return_if_fail(MAFW_IS_PLAYLIST(playlist)); + g_return_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator)); + + if (iterator->priv->playlist == NULL) { + g_critical("Got playlist:item-moved but renderer has not a " \ + "playlist assigned! Skipping..."); + return; + } + + play_index = iterator->priv->current_index; + + if (play_index == from) { + /* So the current item has been moved, let's update the + the current index to the new location */ + mafw_playlist_iterator_move_to_index(iterator, to, &error); + } else if (play_index > from && play_index <= to) { + /* So we current item has been pushed one position towards + the head, let's update the current index */ + mafw_playlist_iterator_move_to_prev(iterator, &error); + } else if (play_index >= to && play_index < from) { + /* So we current item has been pushed one position towards + the head, let's update the current index */ + mafw_playlist_iterator_move_to_next(iterator, &error); + } + + if (error != NULL) { + g_critical("playlist::item-moved handler failed " + "with \"%s\"", error->message); + g_error_free (error); + } +} + +MafwPlaylistIterator * +mafw_playlist_iterator_new(void) +{ + MafwPlaylistIterator *iterator = (MafwPlaylistIterator *) + g_object_new(MAFW_TYPE_PLAYLIST_ITERATOR, NULL); + + g_assert(iterator != NULL); + + iterator->priv->playlist = NULL; + iterator->priv->current_index = -1; + iterator->priv->current_objectid = NULL; + iterator->priv->size = -1; + + return iterator; +} + +void +mafw_playlist_iterator_initialize(MafwPlaylistIterator *iterator, + MafwPlaylist *playlist, GError **error) +{ + guint size; + gint index = -1; + gchar *objectid = NULL; + GError *new_error = NULL; + + g_return_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator)); + g_return_if_fail(iterator->priv->playlist == NULL); + + iterator->priv->size = -1; + + mafw_playlist_get_starting_index(playlist, (guint *) &index, &objectid, + &new_error); + + if (new_error == NULL) { + size = mafw_playlist_get_size(playlist, &new_error); + } + + if (new_error == NULL) { + iterator->priv->playlist = g_object_ref(playlist); + iterator->priv->current_index = index; + iterator->priv->current_objectid = objectid; + iterator->priv->size = size; + + g_signal_connect(playlist, + "item-moved", + G_CALLBACK(mafw_playlist_iterator_playlist_item_moved_handler), + iterator); + g_signal_connect(playlist, + "contents-changed", + G_CALLBACK(mafw_playlist_iterator_playlist_contents_changed_handler), + iterator); + } + else { + g_propagate_error (error, new_error); + } +} + +void +mafw_playlist_iterator_invalidate(MafwPlaylistIterator *iterator) +{ + g_return_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator)); + + if (iterator->priv->playlist != NULL) { + g_signal_handlers_disconnect_matched(iterator->priv->playlist, + (GSignalMatchType) G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, + mafw_playlist_iterator_playlist_item_moved_handler, + NULL); + + g_signal_handlers_disconnect_matched(iterator->priv->playlist, + (GSignalMatchType) G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, + mafw_playlist_iterator_playlist_contents_changed_handler, + NULL); + + g_object_unref(iterator->priv->playlist); + g_free(iterator->priv->current_objectid); + iterator->priv->playlist = NULL; + iterator->priv->current_index = -1; + iterator->priv->current_objectid = NULL; + iterator->priv->size = -1; + } +} + +gboolean +mafw_playlist_iterator_is_valid(MafwPlaylistIterator *iterator) +{ + g_return_val_if_fail(MAFW_IS_PLAYLIST_ITERATOR(iterator), FALSE); + + return iterator->priv->playlist != NULL; +} + +void +mafw_playlist_iterator_reset(MafwPlaylistIterator *iterator, GError **error) +{ + gint index = -1; + gchar *objectid = NULL; + GError *new_error = NULL; + + g_return_if_fail(mafw_playlist_iterator_is_valid(iterator)); + + mafw_playlist_get_starting_index(iterator->priv->playlist, + (guint *) &index, + &objectid, &new_error); + + if (new_error == NULL) { + mafw_playlist_iterator_set_data(iterator, index, objectid); + } + else { + g_propagate_error (error, new_error); + } +} + +void +mafw_playlist_iterator_move_to_last(MafwPlaylistIterator *iterator, + GError **error) +{ + GError *new_error = NULL; + gint index = -1; + gchar *objectid = NULL; + + g_return_if_fail(mafw_playlist_iterator_is_valid(iterator)); + + mafw_playlist_get_last_index(iterator->priv->playlist, + (guint *) &index, + &objectid, &new_error); + + if (new_error == NULL) { + mafw_playlist_iterator_set_data(iterator, index, objectid); + } + else { + g_propagate_error (error, new_error); + } +} + +MafwPlaylistIteratorMovementResult +mafw_playlist_iterator_move_to_next(MafwPlaylistIterator *iterator, + GError **error) +{ + return mafw_playlist_iterator_move_to_next_in_direction(iterator, + mafw_playlist_get_next, + error); +} + +MafwPlaylistIteratorMovementResult +mafw_playlist_iterator_move_to_prev(MafwPlaylistIterator *iterator, + GError **error) +{ + return mafw_playlist_iterator_move_to_next_in_direction(iterator, + mafw_playlist_get_prev, + error); +} + +MafwPlaylistIteratorMovementResult +mafw_playlist_iterator_move_to_index(MafwPlaylistIterator *iterator, + gint index, + GError **error) +{ + GError *new_error = NULL; + MafwPlaylistIteratorMovementResult iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_OK; + gint playlist_size; + + g_return_val_if_fail(mafw_playlist_iterator_is_valid(iterator), + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_INVALID); + + playlist_size = mafw_playlist_iterator_get_size(iterator, &new_error); + + if (new_error != NULL) { + g_propagate_error(error, new_error); + iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_ERROR; + } else if ((index < 0) || (index >= playlist_size)) { + iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_LIMIT; + } else { + gchar *objectid = + mafw_playlist_get_item(iterator->priv->playlist, + index, + &new_error); + + if (new_error != NULL) { + g_propagate_error(error, new_error); + iterator_movement_result = + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_ERROR; + } else { + mafw_playlist_iterator_set_data(iterator, index, objectid); + } + } + + return iterator_movement_result; +} + +void +mafw_playlist_iterator_update(MafwPlaylistIterator *iterator, GError **error) +{ + GError *new_error = NULL; + gchar *objectid = NULL; + + objectid = + mafw_playlist_get_item(iterator->priv->playlist, + iterator->priv->current_index, + &new_error); + + if (new_error != NULL) { + g_propagate_error(error, new_error); + } else { + mafw_playlist_iterator_set_data(iterator, + iterator->priv->current_index, + objectid); + } +} + +const gchar * +mafw_playlist_iterator_get_current_objectid(MafwPlaylistIterator *iterator) +{ + g_return_val_if_fail(mafw_playlist_iterator_is_valid(iterator), NULL); + + return iterator->priv->current_objectid; +} + +gint +mafw_playlist_iterator_get_current_index(MafwPlaylistIterator *iterator) +{ + g_return_val_if_fail(mafw_playlist_iterator_is_valid(iterator), 0); + + return iterator->priv->current_index; +} + +gint +mafw_playlist_iterator_get_size(MafwPlaylistIterator *iterator, + GError **error) +{ + g_return_val_if_fail(mafw_playlist_iterator_is_valid(iterator), -1); + + if (iterator->priv->size == -1) { + iterator->priv->size = + mafw_playlist_get_size(iterator->priv->playlist, + error); + } + + return iterator->priv->size; +} diff --git a/libmafw-gst-renderer/mafw-playlist-iterator.h b/libmafw-gst-renderer/mafw-playlist-iterator.h new file mode 100644 index 0000000..6e88360 --- /dev/null +++ b/libmafw-gst-renderer/mafw-playlist-iterator.h @@ -0,0 +1,95 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_PLAYLIST_ITERATOR_H +#define MAFW_PLAYLIST_ITERATOR_H + +#include +#include + +G_BEGIN_DECLS + +typedef struct _MafwPlaylistIteratorPrivate MafwPlaylistIteratorPrivate; + +typedef struct { + GObject g_object; + + MafwPlaylistIteratorPrivate *priv; +} MafwPlaylistIterator; + +typedef struct { + GObjectClass g_object_class; + + /* Signals */ + void (*playlist_changed)(MafwPlaylistIterator *iterator, + gboolean current_item_changed, + GQuark domain, gint code, const gchar *message); +} MafwPlaylistIteratorClass; + +typedef enum { + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_OK, + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_LIMIT, + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_INVALID, + MAFW_PLAYLIST_ITERATOR_MOVE_RESULT_ERROR, +} MafwPlaylistIteratorMovementResult; + +#define MAFW_TYPE_PLAYLIST_ITERATOR \ + (mafw_playlist_iterator_get_type()) +#define MAFW_PLAYLIST_ITERATOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAFW_TYPE_PLAYLIST_ITERATOR, MafwPlaylistIterator)) +#define MAFW_IS_PLAYLIST_ITERATOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAFW_TYPE_PLAYLIST_ITERATOR)) +#define MAFW_PLAYLIST_ITERATOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAFW_TYPE_PLAYLIST_ITERATOR, MafwPlaylistIterator)) +#define MAFW_PLAYLIST_ITERATOR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_PLAYLIST_ITERATOR, \ + MafwPlaylistIteratorClass)) +#define MAFW_IS_PLAYLIST_ITERATOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_PLAYLIST_ITERATOR)) + +G_END_DECLS + +GType mafw_playlist_iterator_get_type(void); +MafwPlaylistIterator *mafw_playlist_iterator_new(void); +void mafw_playlist_iterator_initialize(MafwPlaylistIterator *iterator, + MafwPlaylist *playlist, + GError **error); +void mafw_playlist_iterator_invalidate(MafwPlaylistIterator *iterator); +gboolean mafw_playlist_iterator_is_valid(MafwPlaylistIterator *iterator); +void mafw_playlist_iterator_reset(MafwPlaylistIterator *iterator, GError **error); +void mafw_playlist_iterator_move_to_last(MafwPlaylistIterator *iterator, GError **error); +MafwPlaylistIteratorMovementResult mafw_playlist_iterator_move_to_next(MafwPlaylistIterator *iterator, + GError **error); +MafwPlaylistIteratorMovementResult mafw_playlist_iterator_move_to_prev(MafwPlaylistIterator *iterator, + GError **error); +MafwPlaylistIteratorMovementResult mafw_playlist_iterator_move_to_index(MafwPlaylistIterator *iterator, + gint index, + GError **error); +void mafw_playlist_iterator_update(MafwPlaylistIterator *iterator, GError **error); +const gchar *mafw_playlist_iterator_get_current_objectid(MafwPlaylistIterator *iterator); +gint mafw_playlist_iterator_get_current_index(MafwPlaylistIterator *iterator); +gint mafw_playlist_iterator_get_size(MafwPlaylistIterator *iterator, + GError **error); + +#endif diff --git a/mafw-gst-renderer-uninstalled.pc.in b/mafw-gst-renderer-uninstalled.pc.in new file mode 100644 index 0000000..8496267 --- /dev/null +++ b/mafw-gst-renderer-uninstalled.pc.in @@ -0,0 +1,11 @@ +prefix= +exec_prefix= +libdir=${pcfiledir}/libmafw-gst-renderer +includedir=${pcfiledir}/ + +Name: mafw-gst-renderer +Description: MAFW local renderer +Version: @VERSION@ +Libs: ${libdir}/mafw-gst-renderer.la +Cflags: -I${includedir} +Requires: gobject-2.0 gstreamer-0.10 mafw diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..a894fbf --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,60 @@ +# +# Makefile.am for MAFW gst renderer library. +# +# Author: Visa Smolander +# +# Copyright (C) 2007, 2008, 2009 Nokia. All rights reserved. + +TESTS = check-mafw-gst-renderer +TESTS_ENVIRONMENT = CK_FORK=yes \ + TESTS_DIR=@abs_srcdir@ + +noinst_PROGRAMS = $(TESTS) + +AM_CFLAGS = $(_CFLAGS) +AM_LDFLAGS = $(_LDFLAGS) + +INCLUDES = -I$(top_srcdir)/libmafw-gst-renderer \ + $(DEPS_CFLAGS) \ + $(DEPS_TESTS_CFLAGS) \ + $(CHECKMORE_CFLAGS) + +LDADD = $(CHECKMORE_LIBS) \ + $(DEPS_LIBS) \ + $(DEPS_TESTS_LIBS) \ + $(top_builddir)/libmafw-gst-renderer/mafw-gst-renderer.la \ + -lgstinterfaces-0.10 -lgsttag-0.10 + +if HAVE_GDKPIXBUF +INCLUDES += $(GDKPIXBUF_CFLAGS) +LDADD += $(GDKPIXBUF_LIBS) +endif + +if HAVE_CONIC +INCLUDES += $(CONIC_CFLAGS) +LDADD += $(CONIC_LIBS) +endif + +EXTRA_DIST = media/test.wav media/test.avi media/testframe.png + +# ----------------------------------------------- +# Test programs build specs +# ----------------------------------------------- + +check_mafw_gst_renderer_SOURCES = check-main.c \ + check-mafw-gst-renderer.c \ + mafw-mock-playlist.c mafw-mock-playlist.h \ + mafw-mock-pulseaudio.c mafw-mock-pulseaudio.h + +CLEANFILES = $(TESTS) mafw.db *.gcno *.gcda +MAINTAINERCLEANFILES = Makefile.in + +# Run valgrind on tests. +VG_OPTS := --suppressions=test.suppressions --tool=memcheck \ + --leak-check=full --show-reachable=yes +vg: $(TESTS) + for p in $^; do \ + G_SLICE=always-malloc G_DEBUG=gc-friendly WAIT_TIMEOUT=25000 \ + libtool --mode=execute valgrind $(VG_OPTS) $$p 2>vglog.$$p; \ + done; + -rm -f vgcore.* diff --git a/tests/check-mafw-gst-renderer.c b/tests/check-mafw-gst-renderer.c new file mode 100644 index 0000000..ae4982b --- /dev/null +++ b/tests/check-mafw-gst-renderer.c @@ -0,0 +1,4174 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/* + * check-gst-renderer.c + * + * Gst Renderer unit tests + * + * Copyright (C) 2007 Nokia Corporation + * + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "config.h" + +#include "mafw-gst-renderer.h" +#include "mafw-mock-playlist.h" +#include "mafw-mock-pulseaudio.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "check-mafw-gstreamer-renderer" + +#define SAMPLE_AUDIO_CLIP "test.wav" +#define SAMPLE_VIDEO_CLIP "test.avi" +#define SAMPLE_IMAGE "testframe.png" + +/* Base timeout used when waiting for state transitions or execution of + user function callbacks associated to each mafw-renderer function */ +#define DEFAULT_WAIT_TOUT 2000 + +/* EOS timeout must be longer than the clip duration */ +#define EOS_TIMEOUT 7000 + +SRunner *configure_tests(void); + +typedef struct { + gint index; + MafwPlayState state; +} RendererInfo; + +typedef struct { + gboolean called; + gboolean error; + gint err_code; + gchar *err_msg; + gint seek_position; + gboolean error_signal_expected; + GError *error_signal_received; + const gchar *property_expected; + GValue *property_received; +} CallbackInfo; + +typedef struct { + const gchar *expected_key; + GValue *value; +} MetadataChangedInfo; + +typedef struct { + const gchar *expected; + GValue *received; +} PropertyChangedInfo; + +typedef struct { + gboolean requested; + gboolean received; + gfloat value; +} BufferingInfo; + +static gint wait_tout_val; + +/* Globals. */ + +static MafwRenderer *g_gst_renderer = NULL; + +/* Error messages. */ + +static const gchar *callback_err_msg = "Error received when %s: (%d) %s"; +static const gchar *callback_no_err_msg = "No error received when %s: (%d) %s"; +static const gchar *no_callback_msg = "We forgot to call the user callback"; +static const gchar *state_err_msg = "Call %s didn't change state to %s. " \ +"Current state is: %d"; +static const gchar *index_err_msg = "Actual index is (%d) instead of the " \ +"expected index (%d)"; + + +/*---------------------------------------------------------------------------- + Signal handlers + ----------------------------------------------------------------------------*/ + + +static void error_cb(MafwRenderer *s, GQuark domain, gint code, gchar *msg, + gpointer user_data) +{ + CallbackInfo* c = (CallbackInfo*) user_data; + + /* "MafwExtension::error" signal handler */ + if (user_data == NULL || !c->error_signal_expected) { + fail("Signal error received: (%d) %s", code, msg); + } else { + if (c->error_signal_received != NULL) { + fail("Error received already initialized"); + } else { + c->error_signal_received = + g_error_new_literal(domain, code, msg); + } + } +} + +static void state_changed_cb(MafwRenderer *s, MafwPlayState state, + gpointer user_data) +{ + /* "MafwRenderer::state-changed" signal handler */ + RendererInfo *si = (RendererInfo *) user_data; + gchar *states[] = {"Stopped","Playing","Paused","Transitioning"}; + + si->state = state; + g_debug("state changed (%s) ---", states[state]); +} + +static gboolean media_changed_called; + +static void media_changed_cb(MafwRenderer *s, gint index, gchar *objectid, + gpointer user_data) +{ + /* "MafwRenderer::media-changed" signal handler */ + RendererInfo *si = (RendererInfo *) user_data; + + si->index = index; + g_debug("media changed (%d) ---", index); + media_changed_called = TRUE; +} +static void playlist_changed_cb (MafwRenderer *self, + GObject *playlist, + gpointer user_data) +{ + g_debug("playlist changed"); + fail_if(media_changed_called, "At first playlist-changed should be called"); +} + +static void metadata_changed_cb(MafwRenderer *self, const gchar *key, + GValueArray *value, gpointer user_data) +{ + MetadataChangedInfo *m = user_data; + + if (m->expected_key != NULL && strcmp(key, m->expected_key) == 0) + { + GValue *original; + + original = g_value_array_get_nth(value, 0); + + m->value = g_new0(GValue, 1); + g_value_init(m->value, G_VALUE_TYPE(original)); + g_value_copy(original, m->value); + } +} + +static void property_changed_cb(MafwExtension *extension, const gchar *name, + const GValue *value, gpointer user_data) +{ + PropertyChangedInfo* p = (PropertyChangedInfo*) user_data; + gchar *value_string; + + value_string = g_strdup_value_contents(value); + + g_debug("property_changed_cb: %s (%s)", name, value_string); + g_free(value_string); + + if (p->expected != NULL && + strcmp(p->expected, name) == 0) { + p->received = g_new0(GValue, 1); + g_value_init(p->received, G_VALUE_TYPE(value)); + g_value_copy(value, p->received); + } +} + +static void buffering_info_cb(MafwRenderer *self, gfloat status, + gpointer user_data) +{ + BufferingInfo *b = user_data; + + if (b->requested) { + b->received = TRUE; + b->value = status; + } +} + +/*---------------------------------------------------------------------------- + Function callbacks + ----------------------------------------------------------------------------*/ + + +static void status_cb(MafwRenderer* renderer, MafwPlaylist* playlist, guint index, + MafwPlayState state, + const gchar* object_id, + gpointer user_data, + const GError *error) +{ + /* MafwRendererStatusCB */ + RendererInfo* s = (RendererInfo*) user_data; + g_assert(s != NULL); + + if (error != NULL) { + fail("Error received while trying to get renderer status: (%d) %s", + error->code, error->message); + } + s->state = state; + +} + +static void playback_cb(MafwRenderer* renderer, gpointer user_data, const GError* error) +{ + /* MafwRendererPlaybackCB: + + Called after mafw_renderer_play(), mafw_renderer_play_uri(), + mafw_renderer_play_object(), mafw_renderer_stop(), mafw_renderer_pause(), + mafw_renderer_resume(), mafw_renderer_next(), mafw_renderer_previous() or + mafw_renderer_goto_index() has been called. */ + CallbackInfo* c = (CallbackInfo*) user_data; + g_assert(c != NULL); + + c->called = TRUE; + if (error != NULL) { + c->error = TRUE; + c->err_code = error->code; + c->err_msg = g_strdup(error->message); + } +} + +static void seek_cb (MafwRenderer *self, gint position, gpointer user_data, + const GError *error) +{ + /* Called when seeking */ + + CallbackInfo* c = (CallbackInfo*) user_data; + g_assert(c != NULL); + + c->called = TRUE; + c->seek_position = position; + if (error != NULL) { + c->error = TRUE; + c->err_code = error->code; + c->err_msg = g_strdup(error->message); + } +} + +static void get_position_cb(MafwRenderer *self, gint position, + gpointer user_data, const GError *error) +{ + CallbackInfo* c = (CallbackInfo*) user_data; + + g_debug("get position cb: %d", position); + + if (error != NULL) { + c->error = TRUE; + c->err_code = error->code; + c->err_msg = g_strdup(error->message); + } + c->called = TRUE; +} + +static void get_property_cb(MafwExtension *self, + const gchar *name, + GValue *value, + gpointer user_data, + const GError *error) +{ + CallbackInfo* c = (CallbackInfo*) user_data; + gchar *value_string; + + value_string = g_strdup_value_contents(value); + + g_debug("get property cb: %s (%s)", name, value_string); + g_free(value_string); + + if (error != NULL) { + c->error = TRUE; + c->err_code = error->code; + c->err_msg = g_strdup(error->message); + } + + if (c->property_expected != NULL && + strcmp(c->property_expected, name) == 0) { + c->property_received = g_new0(GValue, 1); + g_value_init(c->property_received, G_VALUE_TYPE(value)); + g_value_copy(value, c->property_received); + + c->called = TRUE; + } +} + +/*---------------------------------------------------------------------------- + Helpers + ----------------------------------------------------------------------------*/ + +static gchar *get_sample_clip_path(const gchar *clip) +{ + gchar *my_dir, *media; + + /* Makefile.am sets TESTS_DIR, required for VPATH builds (like make + * distcheck). Otherwise assume we are running in-place. */ + my_dir = g_strdup(g_getenv("TESTS_DIR")); + if (!my_dir) + my_dir = g_get_current_dir(); + media = g_strconcat("file://", my_dir, G_DIR_SEPARATOR_S, + "media" G_DIR_SEPARATOR_S, clip, + NULL); + g_free(my_dir); + return media; +} + +static gchar *get_sample_clip_objectid(const gchar *clip) +{ + gchar *path = NULL; + gchar *objectid = NULL; + + path = get_sample_clip_path(clip); + objectid = mafw_source_create_objectid(path); + g_free(path); + + return objectid; +} + +static gboolean stop_wait_timeout(gpointer user_data) +{ + gboolean *do_stop = (gboolean *) user_data; + g_debug("stop wait timeout"); + *do_stop = TRUE; + + return FALSE; +} + +static gboolean wait_until_timeout_finishes(guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + gboolean result = FALSE; + + g_debug("Init wait_"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + while(!stop_wait) { + result= g_main_context_iteration(NULL, TRUE); + } + + g_debug("End wait_"); + return TRUE; +} + +static gboolean wait_for_state(RendererInfo *renderer_info, + MafwPlayState expected_state, guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + + g_debug("Init wait for state"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + + while(renderer_info->state != expected_state && !stop_wait) { + g_main_context_iteration(NULL, TRUE); + } + + if (!stop_wait) { + g_source_remove(timeout); + } + + g_debug("End wait for state"); + return (renderer_info->state == expected_state); +} + +static gboolean wait_for_callback(CallbackInfo *callback, guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + + g_debug("Init wait for callback"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + + while (callback->called == FALSE && !stop_wait) { + g_main_context_iteration(NULL, TRUE); + } + if (!stop_wait) { + g_source_remove(timeout); + } + g_debug("End wait for callback"); + return callback->called; +} + +static gboolean wait_for_metadata(MetadataChangedInfo *callback, guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + + g_debug("Init wait for metadata"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + + while (callback->value == NULL && !stop_wait) { + g_main_context_iteration(NULL, TRUE); + } + if (!stop_wait) { + g_source_remove(timeout); + } + g_debug("End wait for metadata"); + return callback->value != NULL; +} + +static gboolean wait_for_property(PropertyChangedInfo *callback, guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + + g_debug("Init wait for property changed"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + + while (callback->received == NULL && !stop_wait) { + g_main_context_iteration(NULL, TRUE); + } + if (!stop_wait) { + g_source_remove(timeout); + } + g_debug("End wait for callback"); + return callback->received != NULL; +} + +static gboolean wait_for_buffering(BufferingInfo *callback, guint millis) +{ + guint timeout = 0; + gboolean stop_wait = FALSE; + + g_debug("Init wait for buffering info"); + /* We'll wait a limitted ammount of time */ + timeout = g_timeout_add(millis, stop_wait_timeout, &stop_wait); + + while (!callback->received && !stop_wait) { + g_main_context_iteration(NULL, TRUE); + } + if (!stop_wait) { + g_source_remove(timeout); + } + g_debug("End wait for buffering info"); + return callback->received; +} + +static void reset_callback_info(CallbackInfo *callback_info) +{ + if (callback_info->err_msg != NULL) + g_free(callback_info->err_msg); + + callback_info->err_msg = NULL; + callback_info->called = FALSE; + callback_info->error = FALSE; + callback_info->seek_position = 0; + callback_info->error_signal_expected = FALSE; + if (callback_info->error_signal_received != NULL) { + g_error_free(callback_info->error_signal_received); + callback_info->error_signal_received = NULL; + } + callback_info->property_expected = NULL; + if (callback_info->property_received != NULL) { + g_value_unset(callback_info->property_received); + callback_info->property_received = NULL; + } +} + +/*---------------------------------------------------------------------------- + Fixtures + ----------------------------------------------------------------------------*/ + +static void fx_setup_dummy_gst_renderer(void) +{ + MafwRegistry *registry; + + /* Setup GLib */ + g_type_init(); + + /* Create a gst renderer instance */ + registry = MAFW_REGISTRY(mafw_registry_get_instance()); + fail_if(registry == NULL, + "Error: cannot get MAFW registry"); + + g_gst_renderer = MAFW_RENDERER(mafw_gst_renderer_new(registry)); + fail_if(!MAFW_IS_GST_RENDERER(g_gst_renderer), + "Could not create gst renderer instance"); +} + +static void fx_teardown_dummy_gst_renderer(void) +{ + g_object_unref(g_gst_renderer); +} + +/*---------------------------------------------------------------------------- + Mockups + ----------------------------------------------------------------------------*/ + +/* GStreamer mock */ + +GstElement * gst_element_factory_make(const gchar * factoryname, + const gchar * name) +{ + GstElementFactory *factory; + GstElement *element; + const gchar *use_factoryname; + + g_return_val_if_fail(factoryname != NULL, NULL); + + /* For testing, use playbin instead of playbin2 */ + if (g_ascii_strcasecmp(factoryname, "playbin2") == 0) + use_factoryname = "playbin"; + else + use_factoryname = factoryname; + + GST_LOG("gstelementfactory: make \"%s\" \"%s\"", + use_factoryname, GST_STR_NULL (name)); + + factory = gst_element_factory_find(use_factoryname); + if (factory == NULL) { + /* No factory */ + GST_INFO("no such element factory \"%s\"!", use_factoryname); + return NULL; + } + + GST_LOG_OBJECT(factory, "found factory %p", factory); + if (g_ascii_strcasecmp(use_factoryname, "pulsesink") == 0) { + element = gst_element_factory_make("fakesink", "pulsesink"); + g_object_set(G_OBJECT(element), "sync", TRUE, NULL); + } else if (g_ascii_strcasecmp(use_factoryname, "xvimagesink") == 0) { + element = gst_element_factory_make("fakesink", "xvimagesink"); + g_object_set(G_OBJECT(element), "sync", TRUE, NULL); + } else { + element = gst_element_factory_create(factory, name); + } + gst_object_unref(factory); + + if (element == NULL) { + /* Create failed */ + GST_INFO_OBJECT(factory, "couldn't create instance!"); + return NULL; + } + + GST_LOG("gstelementfactory: make \"%s\" \"%s\"",use_factoryname, + GST_STR_NULL(name)); + + /* Playbin will use fake renderer */ + if (g_ascii_strcasecmp(use_factoryname, "playbin") == 0) { + GstElement *audiorenderer = gst_element_factory_make("fakesink", + "audiorenderer"); + + g_object_set(G_OBJECT(audiorenderer), "sync", TRUE, NULL); + g_object_set(G_OBJECT(element), + "audio-sink", + audiorenderer, + NULL); + g_object_set(G_OBJECT(element), + "video-sink", + audiorenderer, + NULL); + } + + return element; +} + + +/*---------------------------------------------------------------------------- + Test cases + ----------------------------------------------------------------------------*/ + +START_TEST(test_basic_playback) +{ + RendererInfo s; + CallbackInfo c; + MetadataChangedInfo m; + GstBus *bus = NULL; + GstMessage *message = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + m.expected_key = NULL; + m.value = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "metadata-changed", + G_CALLBACK(metadata_changed_cb), + &m); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + /* No media item has been set so, we should get an error */ + if (c.error == FALSE) + fail("Play of unset media did not return an error"); + } else { + fail(no_callback_msg); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + gchar *objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Transitioning", + s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + g_free(objectid); + + /* --- Get position --- */ + + reset_callback_info(&c); + + mafw_renderer_get_position(g_gst_renderer, get_position_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_position", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Duration emission --- */ + + m.expected_key = MAFW_METADATA_KEY_DURATION; + + bus = MAFW_GST_RENDERER(g_gst_renderer)->worker->bus; + fail_if(bus == NULL, "No GstBus"); + + message = gst_message_new_duration(NULL, GST_FORMAT_TIME, + 5 * GST_SECOND); + gst_bus_post(bus, message); + + if (wait_for_metadata(&m, wait_tout_val) == FALSE) { + fail("Expected " MAFW_METADATA_KEY_DURATION + ", but not received"); + } + + fail_if(m.value == NULL, "Metadata " MAFW_METADATA_KEY_DURATION + " not received"); + + g_value_unset(m.value); + g_free(m.value); + m.value = NULL; + m.expected_key = NULL; + + /* --- Pause --- */ + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_pause", "Paused", s.state); + } + + /* --- Resume --- */ + + reset_callback_info(&c); + + g_debug("resume..."); + mafw_renderer_resume(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "resuming", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_resume", "Playing", s.state); + } + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop", "Stopped", s.state); + } + +} +END_TEST + +START_TEST(test_playlist_playback) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, }; + CallbackInfo c = {0, }; + gchar *cur_item_oid = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + for (i=0; i<10; i++) { + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + } + g_free(cur_item_oid); + cur_item_oid = get_sample_clip_objectid("unexisting.wav"); + mafw_playlist_insert_item(playlist, 9, cur_item_oid, NULL); + g_free(cur_item_oid); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", s.state); + } + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_stop", "Stopped", s.state); + } + + /* --- Next --- */ + + /* Get actual index */ + + gint initial_index = s.index; + + for (i=0; i<3; i++) { + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != initial_index + (i+1), index_err_msg, s.index, + initial_index + (i+1)); + } + + + /* --- Prev --- */ + + /* Get actual index */ + initial_index = s.index; + + for (i=0; i<3; i++) { + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != initial_index - (i+1), index_err_msg, s.index, + initial_index - (i+1)); + } + + /* Check if renderer remains in Stopped state after some Prev operations */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped state " + "after doing prev. The actual state is %s and must be %s", + s.state, "Stopped"); + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping playback", + c.err_code, c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop","Stopped", s.state); + } + + /* --- Go to index in Stopped state --- */ + + reset_callback_info(&c); + + g_debug("goto index 3..."); + mafw_renderer_goto_index(g_gst_renderer, 3, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 3", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 3, index_err_msg, s.index, 3); + + /* Check if renderer remains in Stopped state after running go to index */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped state " + "after running go to index. The actual state is %s and must be" + " %s", s.state, "Stopped"); + + /* --- Play (playlist index is 3) --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", s.state); + } + + /* --- Goto index in Playing state --- */ + + reset_callback_info(&c); + + g_debug("goto index 5..."); + mafw_renderer_goto_index(g_gst_renderer, 5, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", "Playing", s.state); + } + + /* Check if the index if correct */ + fail_if(s.index != 5, index_err_msg, s.index, 5); + + /* Check if renderer remains in Playing state after running go to index */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail("Gst renderer didn't remain in Playing state after running " + "go to index. The actual state is %s and must be %s", + s.state, "Playing"); + } + + /* --- Goto an invalid index --- */ + + reset_callback_info(&c); + + g_debug("goto the invalid index 20..."); + mafw_renderer_goto_index(g_gst_renderer, 20, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error == FALSE) + fail("Error not received when we go to an incorrect" + "index"); + } else { + fail(no_callback_msg); + } + + /* Check if the previous index (5) remains after an incorrect go to + index request */ + fail_if(s.index != 5, index_err_msg, 5, s.index); + + reset_callback_info(&c); + + /* --- Reassigning playlist --- */ + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, + g_object_ref(playlist), NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Go to index with invalid media --- */ + + reset_callback_info(&c); + + g_debug("goto index 9..."); + mafw_renderer_goto_index(g_gst_renderer, 9, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 9", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 9, index_err_msg, s.index, 9); + + /* Check if renderer remains in Stopped state after running go + * to index */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped " + "state after running go to index. The actual state is %d and " + "must be %s", s.state, "Stopped"); + + /* --- Play (playlist index is 9) --- */ + + reset_callback_info(&c); + + c.error_signal_expected = TRUE; + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Transitioning", + s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", s.state); + } + + fail_if(c.error_signal_received == NULL || + !g_error_matches(c.error_signal_received, MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_INVALID_URI), + "No error received or incorrect one"); + + if (c.error_signal_received != NULL) { + g_error_free(c.error_signal_received); + c.error_signal_received = NULL; + } + c.error_signal_expected = FALSE; + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping playback", + c.err_code, c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop","Stopped", s.state); + } + + /* --- Remove last media --- */ + + mafw_playlist_remove_item(playlist, 10, NULL); + + /* --- Go to index with invalid media --- */ + + reset_callback_info(&c); + + g_debug("goto index 9..."); + mafw_renderer_goto_index(g_gst_renderer, 9, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 9", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 9, index_err_msg, s.index, 9); + + /* Check if renderer remains in Stopped state after running go + * to index */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped " + "state after running go to index. The actual state is %d and " + "must be %s", s.state, "Stopped"); + + /* --- Play (playlist index is 9) --- */ + + reset_callback_info(&c); + + c.error_signal_expected = TRUE; + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Transitioning", + s.state); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Stopped", s.state); + } + + fail_if(c.error_signal_received == NULL || + !g_error_matches(c.error_signal_received, MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_INVALID_URI), + "No error received or incorrect one"); + + if (c.error_signal_received != NULL) { + g_error_free(c.error_signal_received); + c.error_signal_received = NULL; + } + c.error_signal_expected = FALSE; + + /* --- Play incorrect object --- */ + + reset_callback_info(&c); + + c.error_signal_expected = TRUE; + + gchar *objectid = get_sample_clip_objectid("unexisting.wav"); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", + s.state); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Stopped", + s.state); + } + + fail_if(c.error_signal_received == NULL || + !g_error_matches(c.error_signal_received, MAFW_RENDERER_ERROR, + MAFW_RENDERER_ERROR_INVALID_URI), + "No error received or incorrect one"); + + if (c.error_signal_received != NULL) { + g_error_free(c.error_signal_received); + c.error_signal_received = NULL; + } + c.error_signal_expected = FALSE; + + g_free(objectid); + +} +END_TEST + + +START_TEST(test_repeat_mode_playback) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Create playlist --- */ + + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + + /* Set repeat mode */ + mafw_playlist_set_repeat(playlist, TRUE); + + /* --- Assign playlist --- */ + + g_debug("assign playlist..."); + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 9..."); + /* go to the end of the playlist */ + mafw_renderer_goto_index(g_gst_renderer, 9, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 9", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* check if the movement was successful */ + fail_if(s.index != 9, index_err_msg, 9, s.index); + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping playback", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_stop", "Stopped", s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("next..."); + /* The actual index is 9 */ + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* check if the movement was successful */ + fail_if(s.index != 0, index_err_msg, s.index, 0); + + /* Check if renderer remains in Stopped state after moving to next */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped state " + "after doing next. The actual state is %s and must be %s", + s.state, "Stopped"); + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("prev..."); + /* The actual index is 0 */ + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* check if the movement was successful */ + fail_if(s.index != 9, index_err_msg, s.index, 9); + + /* Check if renderer remains in Stopped state after moving to next */ + fail_if(s.state != Stopped, "Gst renderer didn't remain in Stopped state " + "after doing next. The actual state is %s and must be %s", + s.state, "Stopped"); +} +END_TEST + + +START_TEST(test_gst_renderer_mode) +{ + MafwPlaylist *playlist = NULL; + MafwGstRenderer *renderer = NULL; + MafwGstRendererPlaybackMode play_mode; + gchar *objectid = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + gchar *modes[] = {"MAFW_GST_RENDERER_MODE_PLAYLIST", + "MAFW_GST_RENDERER_MODE_STANDALONE"}; + + renderer = MAFW_GST_RENDERER(g_gst_renderer); + + /* Initiliaze callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Create playlist --- */ + + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + + /* --- Assign playlist --- */ + + g_debug("assign playlist..."); + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Transitioning", s.state); + } + + /* Check that renderer is playing a playlist */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", s.state); + } + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_PLAYLIST, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s",objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + g_free(objectid); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Transitioning", + s.state); + } + + /* Check that renderer is playing an object */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_STANDALONE, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + /* Wait EOS_TIMEOUT to ensure that the play_object has finished */ + wait_until_timeout_finishes(EOS_TIMEOUT); + + /* Check that after playing the object, renderer returns to the playlist + playback */ + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_PLAYLIST, + "Incorrect value of playback_mode: %s", modes[play_mode]); + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + g_free(objectid); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Transitioning", + s.state); + } + + /* Check that renderer is playing an object */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_STANDALONE, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + + /* --- Move to next when renderer is playing an object --- */ + + reset_callback_info(&c); + + g_debug("next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check that "next" function finishes the object playback and returns + to the playlist playback */ + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_PLAYLIST, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + /* Check that renderer is still in Playing state */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop", "Stopped", s.state); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Transitioning", + s.state); + } + + /* Check that renderer is playing an object */ + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_STANDALONE, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + /* Wait EOS_TIMEOUT to ensure that object playback finishes */ + wait_until_timeout_finishes(EOS_TIMEOUT); + + /* Check if renderer is in playlist mode and the renderer state is the state before + playing the object */ + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop", "Stopped", s.state); + } + play_mode = mafw_gst_renderer_get_playback_mode(renderer); + fail_if(play_mode != MAFW_GST_RENDERER_MODE_PLAYLIST, + "Incorrect value of playback_mode: %s", modes[play_mode]); + + g_free(objectid); +} +END_TEST + +#define MOCK_SOURCE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST((o), \ + mock_source_get_type(), \ + MockSource)) + +typedef struct { + MafwSourceClass parent; +} MockSourceClass; + +typedef struct { + MafwSource parent; + + +} MockSource; + +GType mock_source_get_type(void); +GObject* mock_source_new(void); + +G_DEFINE_TYPE(MockSource, mock_source, MAFW_TYPE_SOURCE); + +static GHashTable *get_md_ht; /* Metadata hash-table for the metadata result */ +static GError *get_md_err; /* Error value for the metadata result */ +static gboolean set_mdata_called; /* Whether set_metadata was called or not */ +static gboolean get_mdata_called; /* Whether get_metadata was called or not */ +static gint reference_pcount; /* Reference playcount, what should come in set_metadata */ +static gboolean set_for_playcount; /* TRUE, when the set_metadata is called to modify the playcount */ +static gboolean set_for_lastplayed; /* TRUE, when the set_metadata is called to modify the last-played */ + +static void get_metadata(MafwSource *self, + const gchar *object_id, + const gchar *const *metadata, + MafwSourceMetadataResultCb callback, + gpointer user_data) +{ + get_mdata_called = TRUE; + fail_if(strcmp(object_id, "mocksource::test")); + callback(self, object_id, get_md_ht, user_data, get_md_err); +} + +static void set_metadata(MafwSource *self, const gchar *object_id, + GHashTable *metadata, + MafwSourceMetadataSetCb callback, + gpointer user_data) +{ + GValue *curval; + gint htsize = 0; + + if (set_for_playcount) + htsize++; + if (set_for_lastplayed) + htsize++; + fail_if(strcmp(object_id, "mocksource::test")); + fail_if(!metadata); + fail_if(g_hash_table_size(metadata) != htsize, "Hash table size: %d vs %d", g_hash_table_size(metadata), htsize); + if (set_for_playcount) + { + curval = mafw_metadata_first(metadata, + MAFW_METADATA_KEY_PLAY_COUNT); + fail_if(!curval); + fail_if(g_value_get_int(curval) != reference_pcount); + } + if (set_for_lastplayed) + { + curval = mafw_metadata_first(metadata, + MAFW_METADATA_KEY_LAST_PLAYED); + fail_if(!curval); + fail_if(!G_VALUE_HOLDS(curval, G_TYPE_LONG)); + } + set_mdata_called = TRUE; +} + +static void mock_source_class_init(MockSourceClass *klass) +{ + MafwSourceClass *sclass = MAFW_SOURCE_CLASS(klass); + + sclass->get_metadata = get_metadata; + sclass->set_metadata = set_metadata; + +} + +static void mock_source_init(MockSource *source) +{ + /* NOP */ +} + +GObject* mock_source_new(void) +{ + GObject* object; + object = g_object_new(mock_source_get_type(), + "plugin", "mockland", + "uuid", "mocksource", + "name", "mocksource", + NULL); + return object; +} + + +START_TEST(test_update_stats) +{ + MafwGstRenderer *renderer = NULL; + MafwSource *src; + MafwRegistry *registry; + + registry = MAFW_REGISTRY(mafw_registry_get_instance()); + fail_if(registry == NULL, + "Error: cannot get MAFW registry"); + + + renderer = MAFW_GST_RENDERER(g_gst_renderer); + src = MAFW_SOURCE(mock_source_new()); + + mafw_registry_add_extension(registry, MAFW_EXTENSION(src)); + + /* Error on get_mdata_cb*/ + set_for_playcount = FALSE; + set_for_lastplayed = FALSE; + get_md_err = NULL; + g_set_error(&get_md_err, MAFW_SOURCE_ERROR, + MAFW_SOURCE_ERROR_INVALID_OBJECT_ID, + "Wrong object id mocksource::test"); + renderer->media->object_id = g_strdup("mocksource::test"); + mafw_gst_renderer_update_stats(renderer); + g_error_free(get_md_err); + fail_if(set_mdata_called); + fail_if(!get_mdata_called); + + /* get_mdata ok, but HashTable is NULL */ + reference_pcount = 1; + get_mdata_called = FALSE; + set_for_lastplayed = TRUE; + set_for_playcount = TRUE; + get_md_err = NULL; + mafw_gst_renderer_update_stats(renderer); + fail_if(!set_mdata_called); + fail_if(!get_mdata_called); + + /* get_mdata ok, but HashTable is empty */ + get_mdata_called = FALSE; + set_mdata_called = FALSE; + set_for_lastplayed = TRUE; + set_for_playcount = TRUE; + get_md_ht = mafw_metadata_new(); + mafw_gst_renderer_update_stats(renderer); + fail_if(!set_mdata_called); + fail_if(!get_mdata_called); + + /* get_mdata ok, but HashTable has valid value */ + get_mdata_called = FALSE; + set_mdata_called = FALSE; + set_for_lastplayed = TRUE; + set_for_playcount = TRUE; + mafw_metadata_add_int(get_md_ht, + MAFW_METADATA_KEY_PLAY_COUNT, + 1); + reference_pcount = 2; + mafw_gst_renderer_update_stats(renderer); + fail_if(!set_mdata_called); + fail_if(!get_mdata_called); +} +END_TEST + +START_TEST(test_play_state) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + gchar *objectid = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + if (wait_for_state(&s, Stopped, 3000) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Stop", + s.state); + } + + g_free(objectid); + + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + mafw_playlist_set_repeat(playlist, FALSE); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, + NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail("Play after assigning playlist failed"); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", + s.state); + } + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 9, index_err_msg, s.index, 9); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", "Playing", + s.state); + } + + /* Removing last element */ + + g_debug("removing last element..."); + fail_if(mafw_playlist_get_size(playlist, NULL) != 10, + "Playlist should have 10 elements"); + mafw_playlist_remove_item(playlist, 9, NULL); + fail_if(mafw_playlist_get_size(playlist, NULL) != 9, + "Playlist should have 9 elements"); + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_element", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_element", "Playing", + s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 0, index_err_msg, s.index, 0); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", "Playing", + s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 8", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", "Playing", + s.state); + } + + /* --- Seeking --- */ + + reset_callback_info(&c); + + g_debug("seeking..."); + mafw_renderer_set_position(g_gst_renderer, SeekAbsolute, 1, + seek_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "seeking failed", c.err_code, + c.err_msg); + if (c.seek_position != 1) { + fail("seeking failed"); + } + } else { + fail(no_callback_msg); + } + + /* --- Waiting EOS --- */ + + if (wait_for_state(&s, Stopped, 2000) == FALSE) { + fail(state_err_msg, "EOS", "Stop", + s.state); + } +} +END_TEST + +START_TEST(test_pause_state) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + gchar *objectid = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + mafw_playlist_set_repeat(playlist, FALSE); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, + NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail("Play failed"); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", + "Transitioning", s.state); + } + + /* Testing pause in transitioning */ + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Testing resume in transitioning */ + + reset_callback_info(&c); + + g_debug("resume..."); + mafw_renderer_resume(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "resuming", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + reset_callback_info(&c); + + /* Testing resume without having paused in transitioning */ + + reset_callback_info(&c); + + g_debug("resume..."); + mafw_renderer_resume(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "resuming", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_pause", "Paused", + s.state); + } + + /* --- Play object in pause --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_pause", "Paused", + s.state); + } + + g_free(objectid); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail("Play failed"); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", + "Transitioning", s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_pause", "Paused", + s.state); + } + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 9, index_err_msg, s.index, 9); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", + "Transitioning", s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", "Playing", + s.state); + } + + /* Removing last element */ + + g_debug("removing last element..."); + fail_if(mafw_playlist_get_size(playlist, NULL) != 10, + "Playlist should have 10 elements"); + mafw_playlist_remove_item(playlist, 9, NULL); + fail_if(mafw_playlist_get_size(playlist, NULL) != 9, + "Playlist should have 9 elements"); + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_item", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_item", "Playing", + s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_item", "Playing", + s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 0, index_err_msg, s.index, 0); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", + "Transitioning", s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", "Playing", + s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 8", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* Check if the playlist index is correct */ + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", + "Transitioning", s.state); + } + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", "Playing", + s.state); + } + + /* --- Seeking --- */ + + reset_callback_info(&c); + + mafw_renderer_set_position(g_gst_renderer, SeekAbsolute, 1, + seek_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "seeking", c.err_code, + c.err_msg); + if (c.seek_position != 1) { + fail("seeking failed"); + } + } else { + fail(no_callback_msg); + } + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop", "Stopped", s.state); + } +} +END_TEST + +START_TEST(test_stop_state) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "going to index 8", + c.err_code, c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + mafw_playlist_set_repeat(playlist, FALSE); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, + NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* Removing last element */ + + g_debug("removing last element..."); + fail_if(mafw_playlist_get_size(playlist, NULL) != 10, + "Playlist should have 10 elements"); + mafw_playlist_remove_item(playlist, 9, NULL); + fail_if(mafw_playlist_get_size(playlist, NULL) != 9, + "Playlist should have 9 elements"); + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 9..."); + mafw_renderer_goto_index(g_gst_renderer, 9, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "going to index 9", + c.err_code, c.err_msg); + } else { + fail(no_callback_msg); + } + reset_callback_info(&c); +} +END_TEST + +START_TEST(test_transitioning_state) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + gchar *objectid = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + mafw_playlist_set_repeat(playlist, FALSE); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, + NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail("Play after assigning playlist failed"); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", + "Transitioning", s.state); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + g_free(objectid); + + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 9, index_err_msg, s.index, 9); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", + "Transitioning", s.state); + } + + /* Removing last element */ + + g_debug("removing last element..."); + fail_if(mafw_playlist_get_size(playlist, NULL) != 10, + "Playlist should have 10 elements"); + mafw_playlist_remove_item(playlist, 9, NULL); + fail_if(mafw_playlist_get_size(playlist, NULL) != 9, + "Playlist should have 9 elements"); + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_playlist_remove_element", + "Transitioning", s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 0, index_err_msg, s.index, 0); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", + "Transitioning", s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 8", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", + "Transitioning", s.state); + } +} +END_TEST + +START_TEST(test_state_class) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + gchar *objectid = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_no_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (!c.error) + fail(callback_err_msg, "going to index 8", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + for (i=0; i<10; i++) { + gchar *cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item( + playlist, i, cur_item_oid, NULL); + g_free(cur_item_oid); + } + mafw_playlist_set_repeat(playlist, FALSE); + + media_changed_called = FALSE; + if (!mafw_renderer_assign_playlist(g_gst_renderer, playlist, + NULL)) + { + fail("Assign playlist failed"); + } + + wait_for_state(&s, Stopped, wait_tout_val); + + /* --- Play object --- */ + + reset_callback_info(&c); + + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Next --- */ + + reset_callback_info(&c); + + g_debug("move to next..."); + mafw_renderer_next(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to next", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 1, index_err_msg, s.index, 1); + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_next", "Playing", + s.state); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Go to index --- */ + + reset_callback_info(&c); + + g_debug("goto index 8..."); + mafw_renderer_goto_index(g_gst_renderer, 8, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "going to index 8", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 8, index_err_msg, s.index, 8); + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_goto_index", "Playing", + s.state); + } + + /* --- Play object --- */ + + reset_callback_info(&c); + + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 7, index_err_msg, s.index, 7); + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", "Playing", + s.state); + } + + /* --- Play --- */ + + reset_callback_info(&c); + + g_debug("play..."); + mafw_renderer_play(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail("Play after assigning playlist failed"); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play", "Playing", + s.state); + } + + /* --- Prev --- */ + + reset_callback_info(&c); + + g_debug("move to prev..."); + mafw_renderer_previous(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "moving to prev", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(s.index != 6, index_err_msg, s.index, 6); + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", + "Transitioning", s.state); + } + + /* --- Seeking --- */ + + reset_callback_info(&c); + + g_debug("seeking..."); + mafw_renderer_set_position(g_gst_renderer, SeekRelative, 1, + seek_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "seeking failed", c.err_code, + c.err_msg); + if (c.seek_position != 1) { + fail("seeking failed"); + } + } else { + fail(no_callback_msg); + } + + /* --- Seeking --- */ + + reset_callback_info(&c); + + g_debug("seeking..."); + mafw_renderer_set_position(g_gst_renderer, SeekAbsolute, -1, + seek_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "seeking failed", c.err_code, + c.err_msg); + if (c.seek_position != -1) { + fail("seeking failed"); + } + } else { + fail(no_callback_msg); + } + + /* --- Seeking --- */ + + reset_callback_info(&c); + + g_debug("seeking..."); + mafw_renderer_set_position(g_gst_renderer, SeekAbsolute, 1, + seek_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "seeking failed", c.err_code, + c.err_msg); + if (c.seek_position != 1) { + fail("seeking failed"); + } + } else { + fail(no_callback_msg); + } +} +END_TEST + +START_TEST(test_playlist_iterator) +{ + MafwPlaylist *playlist = NULL; + gint i = 0; + CallbackInfo c = {0, };; + MafwPlaylistIterator *iterator = NULL; + GError *error = NULL; + gint size; + gint index; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error = FALSE; + reset_callback_info(&c); + + /* --- Create and assign a playlist --- */ + + g_debug("assign playlist..."); + playlist = MAFW_PLAYLIST(mafw_mock_playlist_new()); + + iterator = mafw_playlist_iterator_new(); + mafw_playlist_iterator_initialize(iterator, playlist, &error); + if (error != NULL) { + fail("Error found: %s, %d, %s", + g_quark_to_string(error->domain), + error->code, error->message); + } + + for (i = 0; i < 3; i++) { + gchar *cur_item_oid = NULL; + cur_item_oid = + get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + mafw_playlist_insert_item(playlist, 0, cur_item_oid, NULL); + g_free(cur_item_oid); + } + + size = mafw_playlist_iterator_get_size(iterator, NULL); + fail_if(size != 3, "Playlist should have 3 elements and it has %d", + size); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 2, "Index should be 2 and it is %d", index); + + mafw_playlist_move_item(playlist, 1, 2, NULL); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 1, "Index should be 1 and it is %d", index); + + mafw_playlist_move_item(playlist, 2, 1, NULL); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 2, "Index should be 2 and it is %d", index); + + mafw_playlist_move_item(playlist, 2, 1, NULL); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 1, "Index should be 1 and it is %d", index); + + mafw_playlist_remove_item(playlist, 0, &error); + if (error != NULL) { + fail("Error found: %s, %d, %s", + g_quark_to_string(error->domain), + error->code, error->message); + } + + size = mafw_playlist_iterator_get_size(iterator, NULL); + fail_if(size != 2, "Playlist should have 2 elements and it has %d", + size); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 0, "Index should be 0 and it is %d", index); + + mafw_playlist_iterator_reset(iterator, NULL); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 0, "Index should be 0 and it is %d", index); + + mafw_playlist_remove_item(playlist, 0, &error); + if (error != NULL) { + fail("Error found: %s, %d, %s", + g_quark_to_string(error->domain), + error->code, error->message); + } + + size = mafw_playlist_iterator_get_size(iterator, NULL); + fail_if(size != 1, "Playlist should have 1 elements and it has %d", + size); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != 0, "Index should be 0 and it is %d", index); + + mafw_playlist_remove_item(playlist, 0, &error); + if (error != NULL) { + fail("Error found: %s, %d, %s", + g_quark_to_string(error->domain), + error->code, error->message); + } + + size = mafw_playlist_iterator_get_size(iterator, NULL); + fail_if(size != 0, "Playlist should have 0 elements and it has %d", + size); + index = mafw_playlist_iterator_get_current_index(iterator); + fail_if(index != -1, "Index should be -1 and it is %d", index); + + g_object_unref(iterator); +} +END_TEST + +START_TEST(test_video) +{ + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + MetadataChangedInfo m; + gchar *objectid = NULL; + GstBus *bus = NULL; + GstStructure *structure = NULL; + GstMessage *message = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + m.expected_key = NULL; + m.value = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + g_signal_connect(g_gst_renderer, "metadata-changed", + G_CALLBACK(metadata_changed_cb), + &m); + +#ifdef HAVE_GDKPIXBUF + mafw_extension_set_property_boolean( + MAFW_EXTENSION(g_gst_renderer), + MAFW_PROPERTY_GST_RENDERER_CURRENT_FRAME_ON_PAUSE, + TRUE); +#endif + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_VIDEO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + MAFW_GST_RENDERER(g_gst_renderer)->worker->xid = 0x1; + bus = MAFW_GST_RENDERER(g_gst_renderer)->worker->bus; + fail_if(bus == NULL, "No GstBus"); + + structure = gst_structure_new("prepare-xwindow-id", "width", + G_TYPE_INT, 64, "height", G_TYPE_INT, 32, + NULL); + message = gst_message_new_element(NULL, structure); + gst_bus_post(bus, message); + + /* --- Pause --- */ + + reset_callback_info(&c); + + m.expected_key = MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI; + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", "Playing", + s.state); + } + + if (wait_for_metadata(&m, wait_tout_val) == FALSE) { + fail("Expected " MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI + ", but not received"); + } + + fail_if(m.value == NULL, "Metadata " + MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI " not received"); + + g_value_unset(m.value); + g_free(m.value); + m.value = NULL; + m.expected_key = NULL; + + /* --- Resume --- */ + + reset_callback_info(&c); + + g_debug("resume..."); + mafw_renderer_resume(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "resuming", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + /* --- EOS --- */ + + if (wait_for_state(&s, Stopped, 3000) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Stop", + s.state); + } + + g_free(objectid); +} +END_TEST + +START_TEST(test_media_art) +{ + RendererInfo s = {0, };; + CallbackInfo c = {0, };; + MetadataChangedInfo m; + gchar *objectid = NULL; + GstBus *bus = NULL; + GstMessage *message = NULL; + GstTagList *list = NULL; + GstBuffer *buffer = NULL; + guchar *image = NULL; + gchar *image_path = NULL; + gsize image_length; + GstCaps *caps = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + m.expected_key = NULL; + m.value = NULL; + c.property_expected = NULL; + c.property_received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "media-changed", + G_CALLBACK(media_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "playlist-changed", + G_CALLBACK(playlist_changed_cb), + NULL); + g_signal_connect(g_gst_renderer, "metadata-changed", + G_CALLBACK(metadata_changed_cb), + &m); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play object --- */ + + reset_callback_info(&c); + + objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, + &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + /* --- Pause --- */ + + reset_callback_info(&c); + + g_debug("pause..."); + mafw_renderer_pause(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "pausing", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Paused, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_prev", "Playing", + s.state); + } + + /* Emit image */ + + bus = MAFW_GST_RENDERER(g_gst_renderer)->worker->bus; + fail_if(bus == NULL, "No GstBus"); + + m.expected_key = MAFW_METADATA_KEY_RENDERER_ART_URI; + + image_path = get_sample_clip_path(SAMPLE_IMAGE); + fail_if(!g_file_get_contents(image_path + 7, (gchar **) &image, + &image_length, NULL), + "Could not load test image"); + g_free(image_path); + + buffer = gst_buffer_new(); + gst_buffer_set_data(buffer, image, image_length); + caps = gst_caps_new_simple("image/png", "image-type", + GST_TYPE_TAG_IMAGE_TYPE, + GST_TAG_IMAGE_TYPE_FRONT_COVER, NULL); + gst_buffer_set_caps(buffer, caps); + gst_caps_unref(caps); + + list = gst_tag_list_new(); + gst_tag_list_add(list, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, buffer, + NULL); + + message = gst_message_new_tag(NULL, list); + gst_bus_post(bus, message); + + /* --- Resume --- */ + + reset_callback_info(&c); + + g_debug("resume..."); + mafw_renderer_resume(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "resuming", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + if (wait_for_metadata(&m, wait_tout_val) == FALSE) { + fail("Expected " MAFW_METADATA_KEY_RENDERER_ART_URI + ", but not received"); + } + + fail_if(m.value == NULL, "Metadata " + MAFW_METADATA_KEY_RENDERER_ART_URI " not received"); + + g_value_unset(m.value); + g_free(m.value); + m.value = NULL; + m.expected_key = NULL; + + /* --- EOS --- */ + + if (wait_for_state(&s, Stopped, 3000) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Stop", + s.state); + } + + g_free(objectid); +} +END_TEST + +START_TEST(test_properties_management) +{ + RendererInfo s; + CallbackInfo c = {0, };; + PropertyChangedInfo p; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + p.expected = NULL; + p.received = NULL; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "property-changed", + G_CALLBACK(property_changed_cb), + &p); + + /* Wait for the volume manager to be initialized */ + + /* Volume */ + + p.expected = MAFW_PROPERTY_RENDERER_VOLUME; + + if (!wait_for_property(&p, wait_tout_val)) { + fail("No property %s received", p.expected); + } + + fail_if(p.received == NULL, "No property %s received", + p.expected); + fail_if(p.received != NULL && + g_value_get_uint(p.received) != 48, + "Property with value %d and %d expected", + g_value_get_uint(p.received), 48); + + if (p.received != NULL) { + g_value_unset(p.received); + g_free(p.received); + p.received = NULL; + } + p.expected = NULL; + + /* --- mute --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_RENDERER_MUTE; + + mafw_extension_set_property_boolean(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, TRUE); + + p.expected = MAFW_PROPERTY_RENDERER_MUTE; + +#ifdef MAFW_GST_RENDERER_ENABLE_MUTE + if (!wait_for_property(&p, wait_tout_val)) { + fail("No property %s received", p.expected); + } + + fail_if(p.received == NULL, "No property %s received", + p.expected); + fail_if(p.received != NULL && + g_value_get_boolean(p.received) != TRUE, + "Property with value %d and %d expected", + g_value_get_boolean(p.received), TRUE); +#else + if (wait_for_property(&p, wait_tout_val)) { + fail("Property %s received and it should not have been", + p.expected); + } + + fail_if(p.received != NULL, + "Property %s received and it should not have been", + p.expected); +#endif + + if (p.received != NULL) { + g_value_unset(p.received); + g_free(p.received); + p.received = NULL; + } + p.expected = NULL; + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); +#ifdef MAFW_GST_RENDERER_ENABLE_MUTE + fail_if(c.property_received != NULL && + g_value_get_boolean(c.property_received) != TRUE, + "Property with value %d and %d expected", + g_value_get_boolean(c.property_received), TRUE); +#else + fail_if(c.property_received != NULL && + g_value_get_boolean(c.property_received) != FALSE, + "Property with value %d and %d expected", + g_value_get_boolean(c.property_received), FALSE); +#endif + + /* --- xid --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_RENDERER_XID; + + mafw_extension_set_property_uint(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, 50); + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_uint(c.property_received) != 50, + "Property with value %d and %d expected", + g_value_get_uint(c.property_received), 50); + + /* --- error policy --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_RENDERER_ERROR_POLICY; + + mafw_extension_set_property_uint(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, 1); + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_uint(c.property_received) != 1, + "Property with value %d and %d expected", + g_value_get_uint(c.property_received), 1); + + /* --- autopaint --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_RENDERER_AUTOPAINT; + + mafw_extension_set_property_boolean(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, TRUE); + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_boolean(c.property_received) != TRUE, + "Property with value %d and %d expected", + g_value_get_boolean(c.property_received), TRUE); + + /* --- colorkey --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_RENDERER_COLORKEY; + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_int(c.property_received) != -1, + "Property with value %d and %d expected", + g_value_get_int(c.property_received), -1); + + /* --- current frame on pause --- */ + + reset_callback_info(&c); + + c.property_expected = MAFW_PROPERTY_GST_RENDERER_CURRENT_FRAME_ON_PAUSE; + + mafw_extension_set_property_boolean(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, TRUE); + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_boolean(c.property_received) != TRUE, + "Property with value %d and %d expected", + g_value_get_boolean(c.property_received), TRUE); + + /* --- volume --- */ + + p.expected = MAFW_PROPERTY_RENDERER_VOLUME; + + mafw_extension_set_property_uint(MAFW_EXTENSION(g_gst_renderer), + p.expected, 50); + + if (!wait_for_property(&p, wait_tout_val)) { + fail("No property %s received", p.expected); + } + + fail_if(p.received == NULL, "No property %s received", + p.expected); + fail_if(p.received != NULL && + g_value_get_uint(p.received) != 50, + "Property with value %d and %d expected", + g_value_get_uint(p.received), 50); + + if (p.received != NULL) { + g_value_unset(p.received); + g_free(p.received); + p.received = NULL; + } + p.expected = NULL; + + c.property_expected = MAFW_PROPERTY_RENDERER_VOLUME; + + mafw_extension_get_property(MAFW_EXTENSION(g_gst_renderer), + c.property_expected, get_property_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "get_property", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + fail_if(c.property_received == NULL, + "No property %s received and expected", c.property_expected); + fail_if(c.property_received != NULL && + g_value_get_uint(c.property_received) != 50, + "Property with value %d and %d expected", + g_value_get_uint(c.property_received), 50); + +#ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME + /* Test reconnection to pulse */ + + pa_context_disconnect(pa_context_get_instance()); + + /* Wait for the volume manager to be reinitialized */ + + /* Volume */ + + p.expected = MAFW_PROPERTY_RENDERER_VOLUME; + + if (!wait_for_property(&p, wait_tout_val)) { + fail("No property %s received", p.expected); + } + + fail_if(p.received == NULL, "No property %s received", + p.expected); + fail_if(p.received != NULL && + g_value_get_uint(p.received) != 48, + "Property with value %d and %d expected", + g_value_get_uint(p.received), 48); + + if (p.received != NULL) { + g_value_unset(p.received); + g_free(p.received); + p.received = NULL; + } + p.expected = NULL; + + reset_callback_info(&c); +#endif +} +END_TEST + +START_TEST(test_buffering) +{ + RendererInfo s; + CallbackInfo c; + BufferingInfo b; + GstBus *bus = NULL; + GstMessage *message = NULL; + + /* Initialize callback info */ + c.err_msg = NULL; + c.error_signal_expected = FALSE; + c.error_signal_received = NULL; + c.property_expected = NULL; + c.property_received = NULL; + b.requested = FALSE; + b.received = FALSE; + b.value = 0.0; + + /* Connect to renderer signals */ + g_signal_connect(g_gst_renderer, "error", + G_CALLBACK(error_cb), + &c); + g_signal_connect(g_gst_renderer, "state-changed", + G_CALLBACK(state_changed_cb), + &s); + g_signal_connect(g_gst_renderer, "buffering-info", + G_CALLBACK(buffering_info_cb), + &b); + + /* --- Get initial status --- */ + + reset_callback_info(&c); + + g_debug("get status..."); + mafw_renderer_get_status(g_gst_renderer, status_cb, &s); + + /* --- Play object --- */ + + reset_callback_info(&c); + + gchar *objectid = get_sample_clip_objectid(SAMPLE_AUDIO_CLIP); + g_debug("play_object... %s", objectid); + mafw_renderer_play_object(g_gst_renderer, objectid, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "playing an object", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Transitioning, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", + "Transitioning", s.state); + } + + if (wait_for_state(&s, Playing, wait_tout_val) == FALSE) { + fail(state_err_msg, "mafw_renderer_play_object", "Playing", + s.state); + } + + g_free(objectid); + + /* --- Buffering info --- */ + + b.requested = TRUE; + + bus = MAFW_GST_RENDERER(g_gst_renderer)->worker->bus; + fail_if(bus == NULL, "No GstBus"); + + message = gst_message_new_buffering(NULL, 50); + gst_bus_post(bus, message); + + if (wait_for_buffering(&b, wait_tout_val) == FALSE) { + fail("Expected buffering message but not received"); + } + + fail_if(b.value != 0.5, "Expected buffering 0.50 and received %1.2f", + b.value); + + b.requested = FALSE; + b.received = FALSE; + b.value = 0; + + /* --- Buffering info --- */ + + b.requested = TRUE; + + message = gst_message_new_buffering(NULL, 100); + gst_bus_post(bus, message); + + if (wait_for_buffering(&b, wait_tout_val) == FALSE) { + fail("Expected buffering message but not received"); + } + + fail_if(b.value != 1.0, "Expected buffering 1.00 and received %1.2f", + b.value); + + b.requested = FALSE; + b.received = FALSE; + b.value = 0; + + /* --- Stop --- */ + + reset_callback_info(&c); + + g_debug("stop..."); + mafw_renderer_stop(g_gst_renderer, playback_cb, &c); + + if (wait_for_callback(&c, wait_tout_val)) { + if (c.error) + fail(callback_err_msg, "stopping", c.err_code, + c.err_msg); + } else { + fail(no_callback_msg); + } + + if (wait_for_state(&s, Stopped, wait_tout_val) == FALSE) { + fail(state_err_msg,"mafw_renderer_stop", "Stopped", s.state); + } +} +END_TEST + +/*---------------------------------------------------------------------------- + Suit creation + ----------------------------------------------------------------------------*/ + +SRunner * configure_tests(void) +{ + SRunner *sr = NULL; + Suite *s = NULL; + const gchar *tout = g_getenv("WAIT_TIMEOUT"); + + if (!tout) + wait_tout_val = DEFAULT_WAIT_TOUT; + else + { + wait_tout_val = (gint)strtol(tout, NULL, 0); + if (wait_tout_val<=0) + wait_tout_val = DEFAULT_WAIT_TOUT; + } + + checkmore_wants_dbus(); + mafw_log_init(":error"); + /* Create the suite */ + s = suite_create("MafwGstRenderer"); + + /* Create test cases */ + TCase *tc1 = tcase_create("Playback"); + + /* Create unit tests for test case "Playback" */ + tcase_add_checked_fixture(tc1, fx_setup_dummy_gst_renderer, + fx_teardown_dummy_gst_renderer); +if (1) tcase_add_test(tc1, test_basic_playback); +if (1) tcase_add_test(tc1, test_playlist_playback); +if (1) tcase_add_test(tc1, test_repeat_mode_playback); +if (1) tcase_add_test(tc1, test_gst_renderer_mode); +if (1) tcase_add_test(tc1, test_update_stats); +if (1) tcase_add_test(tc1, test_play_state); +if (1) tcase_add_test(tc1, test_pause_state); +if (1) tcase_add_test(tc1, test_stop_state); +if (1) tcase_add_test(tc1, test_transitioning_state); +if (1) tcase_add_test(tc1, test_state_class); +if (1) tcase_add_test(tc1, test_playlist_iterator); +if (1) tcase_add_test(tc1, test_video); +if (1) tcase_add_test(tc1, test_media_art); +if (1) tcase_add_test(tc1, test_properties_management); +if (1) tcase_add_test(tc1, test_buffering); + + tcase_set_timeout(tc1, 0); + + suite_add_tcase(s, tc1); + + /* Create srunner object with the test suite */ + sr = srunner_create(s); + + return sr; +} + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/tests/check-main.c b/tests/check-main.c new file mode 100644 index 0000000..c5c5172 --- /dev/null +++ b/tests/check-main.c @@ -0,0 +1,53 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + + +/* This must be provided by the test suite implementation */ +SRunner *configure_tests(void); + +int main(void) +{ + int nf = 0; + + /* Configure test suites to be executed */ + SRunner *sr = configure_tests(); + + /* Run tests */ + srunner_run_all(sr, CK_ENV); + + /* Retrieve number of failed tests */ + nf = srunner_ntests_failed(sr); + + /* Free resouces */ + srunner_free(sr); + + /* Return global success or failure */ + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/tests/mafw-mock-playlist.c b/tests/mafw-mock-playlist.c new file mode 100644 index 0000000..0720261 --- /dev/null +++ b/tests/mafw-mock-playlist.c @@ -0,0 +1,321 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include "mafw-mock-playlist.h" + +static GList *pl_list; +static gchar *pl_name; +static gboolean pl_rep; +static gboolean pl_shuffle; + +/* Item manipulation */ + +static gboolean mafw_mock_playlist_insert_item(MafwPlaylist *playlist, + guint index, + const gchar *objectid, + GError **error); + +static gboolean mafw_mock_playlist_remove_item(MafwPlaylist *playlist, + guint index, + GError **error); + +static gchar *mafw_mock_playlist_get_item(MafwPlaylist *playlist, + guint index, GError **error); + +static gboolean mafw_mock_playlist_move_item(MafwPlaylist *playlist, + guint from, guint to, + GError **error); + +static guint mafw_mock_playlist_get_size(MafwPlaylist *playlist, + GError **error); + +static gboolean mafw_mock_playlist_clear(MafwPlaylist *playlist, + GError **error); + +static gboolean mafw_mock_playlist_increment_use_count(MafwPlaylist *playlist, + GError **error); + +static gboolean mafw_mock_playlist_decrement_use_count(MafwPlaylist *playlist, + GError **error); +gboolean mafw_mock_playlist_get_prev(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error); +gboolean mafw_mock_playlist_get_next(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error); +static void mafw_mock_playlist_get_starting_index(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error); +static void mafw_mock_playlist_get_last_index(MafwPlaylist *playlist, + guint *index, gchar **object_id, + GError **error); + +enum { + PROP_0, + PROP_NAME, + PROP_REPEAT, + PROP_IS_SHUFFLED, +}; + +static void set_prop(MafwMockPlaylist *playlist, guint prop, + const GValue *value, GParamSpec *spec) +{ + if (prop == PROP_NAME) { + pl_name = g_value_dup_string(value); + } else if (prop == PROP_REPEAT) { + pl_rep = g_value_get_boolean(value); + } else + G_OBJECT_WARN_INVALID_PROPERTY_ID(playlist, prop, spec); +} + +static void get_prop(MafwMockPlaylist *playlist, guint prop, + GValue *value, GParamSpec *spec) +{ + if (prop == PROP_NAME) { + g_value_take_string(value, pl_name); + } else if (prop == PROP_REPEAT) { + g_value_set_boolean(value, pl_rep); + } else if (prop == PROP_IS_SHUFFLED) { + g_value_set_boolean(value, pl_shuffle); + } else + G_OBJECT_WARN_INVALID_PROPERTY_ID(playlist, prop, spec); +} + +static void mafw_mock_playlist_get_starting_index(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error) +{ + if (g_list_length(pl_list) > 0) { + *index = 0; + *object_id = g_strdup(g_list_nth_data(pl_list, 0)); + } +} + +static void mafw_mock_playlist_get_last_index(MafwPlaylist *playlist, + guint *index, gchar **object_id, + GError **error) +{ + *index = g_list_length(pl_list) - 1; + *object_id = g_strdup(g_list_nth_data(pl_list, *index)); +} + + +gboolean mafw_mock_playlist_get_next(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error) +{ + gint size; + gboolean return_value = TRUE; + + size = g_list_length(pl_list); + + g_return_val_if_fail(size != 0, FALSE); + + if (*index == (size - 1)) { + return_value = FALSE; + } else { + *object_id = g_strdup(g_list_nth_data(pl_list, ++(*index))); + } + + return return_value; +} + +gboolean mafw_mock_playlist_get_prev(MafwPlaylist *playlist, guint *index, + gchar **object_id, GError **error) +{ + gint size; + gboolean return_value = TRUE; + + size = g_list_length(pl_list); + + g_return_val_if_fail(size != 0, FALSE); + + if (*index == 0) { + return_value = FALSE; + } else { + *object_id = g_strdup(g_list_nth_data(pl_list, --(*index))); + } + + return return_value; +} + +static void playlist_iface_init(MafwPlaylistIface *iface) +{ + iface->get_item = mafw_mock_playlist_get_item; + iface->insert_item = mafw_mock_playlist_insert_item; + iface->clear = mafw_mock_playlist_clear; + iface->get_size = mafw_mock_playlist_get_size; + iface->remove_item = mafw_mock_playlist_remove_item; + iface->move_item = mafw_mock_playlist_move_item; + iface->get_starting_index = mafw_mock_playlist_get_starting_index; + iface->get_last_index = mafw_mock_playlist_get_last_index; + iface->get_next = mafw_mock_playlist_get_next; + iface->get_prev = mafw_mock_playlist_get_prev; + iface->increment_use_count = mafw_mock_playlist_increment_use_count; + iface->decrement_use_count = mafw_mock_playlist_decrement_use_count; +} + + +static void mafw_mock_playlist_finalize(GObject *object) +{ + g_debug(__FUNCTION__); + + while (pl_list) + { + g_free(pl_list->data); + pl_list = g_list_delete_link(pl_list, pl_list); + } + +} + +static void mafw_mock_playlist_class_init( + MafwMockPlaylistClass *klass) +{ + GObjectClass *oclass = NULL; + + oclass = G_OBJECT_CLASS(klass); + + oclass->set_property = (gpointer)set_prop; + oclass->get_property = (gpointer)get_prop; + g_object_class_override_property(oclass, PROP_NAME, "name"); + g_object_class_override_property(oclass, PROP_REPEAT, "repeat"); + g_object_class_override_property(oclass, + PROP_IS_SHUFFLED, "is-shuffled"); + + oclass -> finalize = mafw_mock_playlist_finalize; +} + +static void mafw_mock_playlist_init(MafwMockPlaylist *self) +{ +} + + + +G_DEFINE_TYPE_WITH_CODE(MafwMockPlaylist, mafw_mock_playlist, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(MAFW_TYPE_PLAYLIST, + playlist_iface_init)); + + +GObject *mafw_mock_playlist_new(void) +{ + MafwMockPlaylist *self; + + self = g_object_new(MAFW_TYPE_MOCK_PLAYLIST, NULL); + + return G_OBJECT(self); +} + +gboolean mafw_mock_playlist_insert_item(MafwPlaylist *self, guint index, + const gchar *objectid, + GError **error) +{ + pl_list = g_list_insert(pl_list, g_strdup(objectid), index); + + g_signal_emit_by_name(self, "contents-changed", index, 0, 1); + + return TRUE; +} + +gboolean mafw_mock_playlist_remove_item(MafwPlaylist *self, guint index, + GError **error) +{ + GList *element; + + g_return_val_if_fail(g_list_length(pl_list) > 0, FALSE); + + element = g_list_nth(pl_list, index); + g_free(element->data); + pl_list = g_list_delete_link(pl_list, element); + + g_signal_emit_by_name(self, "contents-changed", index, 1, 0); + + return TRUE; +} + +gchar *mafw_mock_playlist_get_item(MafwPlaylist *self, guint index, + GError **error) +{ + gchar *oid = g_list_nth_data(pl_list, index); + + if (oid) + oid = g_strdup(oid); + + return oid; +} + +guint mafw_mock_playlist_get_size(MafwPlaylist *self, GError **error) +{ + return g_list_length(pl_list); +} + +static gboolean mafw_mock_playlist_move_item(MafwPlaylist *playlist, + guint from, guint to, + GError **error) +{ + GList *element_from, *element_to; + gpointer data; + gint size; + + size = g_list_length(pl_list); + + g_return_val_if_fail(size > 0, FALSE); + g_return_val_if_fail(from != to, FALSE); + g_return_val_if_fail((from < size) && (to < size), FALSE); + + element_from = g_list_nth(pl_list, from); + element_to = g_list_nth(pl_list, to); + + data = element_from->data; + element_from->data = element_to->data; + element_to->data = data; + + g_signal_emit_by_name(playlist, "item-moved", from, to); + + return TRUE; +} + +static gboolean mafw_mock_playlist_increment_use_count(MafwPlaylist *playlist, + GError **error) +{ + return TRUE; +} + +static gboolean mafw_mock_playlist_decrement_use_count(MafwPlaylist *playlist, + GError **error) +{ + return TRUE; +} + +gboolean mafw_mock_playlist_clear(MafwPlaylist *self, GError **error) +{ + mafw_mock_playlist_finalize(NULL); + + return TRUE; +} + +/* vi: set noexpandtab ts=8 sw=8 cino=t0,(0: */ diff --git a/tests/mafw-mock-playlist.h b/tests/mafw-mock-playlist.h new file mode 100644 index 0000000..dcc2da1 --- /dev/null +++ b/tests/mafw-mock-playlist.h @@ -0,0 +1,84 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_MOCK_PLAYLIST_H +#define MAFW_MOCK_PLAYLIST_H + +#include +#include +#include + +/*---------------------------------------------------------------------------- + GObject type conversion macros + ----------------------------------------------------------------------------*/ + +#define MAFW_TYPE_MOCK_PLAYLIST \ + (mafw_mock_playlist_get_type()) + +#define MAFW_MOCK_PLAYLIST(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST(obj, MAFW_TYPE_MOCK_PLAYLIST, \ + MafwMockPlaylist)) + +#define MAFW_MOCK_PLAYLIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST(klass, MAFW_TYPE_MOCK_PLAYLIST, \ + MafwMockPlaylistClass)) + +#define MAFW_IS_MOCK_PLAYLIST(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE(obj, MAFW_TYPE_MOCK_PLAYLIST)) + +#define MAFW_IS_MOCK_PLAYLIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAFW_TYPE_MOCK_PLAYLIST)) + +#define MAFW_MOCK_PLAYLIST_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAFW_TYPE_MOCK_PLAYLIST, \ + MafwMockPlaylistClass)) + +/*---------------------------------------------------------------------------- + GObject type definitions + ----------------------------------------------------------------------------*/ + +typedef struct _MafwMockPlaylist MafwMockPlaylist; +typedef struct _MafwMockPlaylistClass MafwMockPlaylistClass; + + +struct _MafwMockPlaylist +{ + GObject parent_instance; + +}; + +struct _MafwMockPlaylistClass +{ + GObjectClass parent_class; + +}; + +/*---------------------------------------------------------------------------- + Shared playlist-specific functions + ----------------------------------------------------------------------------*/ + +GType mafw_mock_playlist_get_type(void); +GObject *mafw_mock_playlist_new(void); + +#endif diff --git a/tests/mafw-mock-pulseaudio.c b/tests/mafw-mock-pulseaudio.c new file mode 100644 index 0000000..1675073 --- /dev/null +++ b/tests/mafw-mock-pulseaudio.c @@ -0,0 +1,295 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "mafw-mock-pulseaudio.h" + +typedef void pa_glib_mainloop; +typedef void pa_mainloop_api; +typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata); +typedef guint pa_context_flags_t; +typedef void pa_spawn_api; +typedef void pa_operation; +typedef guint32 pa_volume_t; +typedef struct { + guint8 channels; + guint map[32]; +} pa_channel_map; +typedef struct { + guint8 channels; + pa_volume_t values[32]; +} pa_cvolume; +typedef struct { + const gchar *name; + pa_channel_map channel_map; + pa_cvolume volume; + const char *device; + gint mute; + gboolean volume_is_absolute; +} pa_ext_stream_restore_info; +typedef void (*pa_ext_stream_restore_read_cb_t)( + pa_context *c, + const pa_ext_stream_restore_info *info, int eol, void *userdata); +typedef void (*pa_ext_stream_restore_subscribe_cb_t)(pa_context *c, + void *userdata); +typedef void (*pa_context_success_cb_t)(pa_context *c, int success, + void *userdata); +enum pa_context_state { + PA_CONTEXT_UNCONNECTED = 0, + PA_CONTEXT_CONNECTING, + PA_CONTEXT_AUTHORIZING, + PA_CONTEXT_SETTING_NAME, + PA_CONTEXT_READY, + PA_CONTEXT_FAILED, + PA_CONTEXT_TERMINATED +}; +struct _pa_context { + pa_context_notify_cb_t state_cb; + gpointer state_cb_userdata; + enum pa_context_state state; + pa_ext_stream_restore_read_cb_t read_cb; + gpointer read_cb_userdata; + pa_ext_stream_restore_subscribe_cb_t subscribe_cb; + gpointer subscribe_cb_userdata; + pa_cvolume volume; + gboolean mute; +}; + +static pa_context *context = NULL; + +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c); +char *pa_get_binary_name(char *s, size_t l); +pa_mainloop_api *pa_glib_mainloop_get_api(pa_glib_mainloop *g); +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name); +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, + void *userdata); +int pa_context_connect(pa_context *c, const char *server, + pa_context_flags_t flags, const pa_spawn_api *api); +gint pa_context_get_state(pa_context *c); +pa_operation *pa_ext_stream_restore2_read(pa_context *c, + pa_ext_stream_restore_read_cb_t cb, + void *userdata); +void pa_operation_unref(pa_operation *o); +pa_volume_t pa_cvolume_max(const pa_cvolume *volume); +void pa_ext_stream_restore_set_subscribe_cb( + pa_context *c, + pa_ext_stream_restore_subscribe_cb_t cb, void *userdata); +gint pa_operation_get_state(pa_operation *o); +void pa_operation_cancel(pa_operation *o); +void pa_glib_mainloop_free(pa_glib_mainloop *g); +pa_cvolume *pa_cvolume_init(pa_cvolume *a); +pa_cvolume *pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v); +pa_operation *pa_ext_stream_restore2_write( + pa_context *c, gint mode, const pa_ext_stream_restore_info *data[], + unsigned n, int apply_immediately, pa_context_success_cb_t cb, + void *userdata); +pa_operation *pa_ext_stream_restore_subscribe(pa_context *c, int enable, + pa_context_success_cb_t cb, + void *userdata); +void pa_context_unref(pa_context *c); + +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) +{ + return (gpointer) 0x1; +} + +char *pa_get_binary_name(char *s, size_t l) +{ + g_strlcpy(s, "mafw-gst-renderer-tests", l); + + return NULL; +} + +pa_mainloop_api *pa_glib_mainloop_get_api(pa_glib_mainloop *g) +{ + return (gpointer) 0x1; +} + +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) +{ + pa_context *c = g_new0(pa_context, 1); + + pa_cvolume_set(&c->volume, 1, 32000); + + context = c; + + return c; +} + +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, + void *userdata) +{ + c->state_cb = cb; + c->state_cb_userdata = userdata; +} + +static gboolean _pa_context_connect_idle(gpointer userdata) +{ + pa_context *c = userdata; + c->state++; + if (c->state_cb != NULL) { + c->state_cb(c, c->state_cb_userdata); + } + return c->state != PA_CONTEXT_READY; +} + +int pa_context_connect(pa_context *c, const char *server, + pa_context_flags_t flags, const pa_spawn_api *api) +{ + g_idle_add(_pa_context_connect_idle, c); + return 1; +} + +gint pa_context_get_state(pa_context *c) +{ + return c->state; +} + +static gboolean _pa_ext_stream_restore2_read_idle(gpointer userdata) +{ + pa_context *c = userdata; + pa_ext_stream_restore_info info = { 0, }; + + info.name = "sink-input-by-media-role:x-maemo"; + pa_cvolume_set(&info.volume, 1, c->volume.values[0]); + info.mute = c->mute; + + c->read_cb(c, &info, 1, c->read_cb_userdata); + + return FALSE; +} + +pa_operation *pa_ext_stream_restore2_read(pa_context *c, + pa_ext_stream_restore_read_cb_t cb, + void *userdata) +{ + c->read_cb = cb; + c->read_cb_userdata = userdata; + g_idle_add(_pa_ext_stream_restore2_read_idle, c); + return (gpointer) 0x1; +} + +void pa_operation_unref(pa_operation *o) +{ +} + +pa_volume_t pa_cvolume_max(const pa_cvolume *volume) +{ + return volume->values[0]; +} + +pa_operation *pa_ext_stream_restore_subscribe(pa_context *c, int enable, + pa_context_success_cb_t cb, + void *userdata) +{ + if (cb != NULL) { + cb(c, TRUE, userdata); + } + return (gpointer) 0x1; +} + +void pa_ext_stream_restore_set_subscribe_cb( + pa_context *c, + pa_ext_stream_restore_subscribe_cb_t cb, void *userdata) +{ + c->subscribe_cb = cb; + c->subscribe_cb_userdata = userdata; +} + +gint pa_operation_get_state(pa_operation *o) +{ + return 1; +} + +void pa_operation_cancel(pa_operation *o) +{ +} + +void pa_context_unref(pa_context *c) +{ + g_free(c); +} + +void pa_glib_mainloop_free(pa_glib_mainloop *g) +{ +} + +pa_cvolume *pa_cvolume_init(pa_cvolume *a) +{ + pa_cvolume_set(a, 1, 0); + return a; +} + +pa_cvolume *pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) +{ + a->channels = 1; + a->values[0] = v; + return a; +} + +static gboolean _pa_ext_stream_restore_write_idle(gpointer userdata) +{ + pa_context *c = userdata; + + if (c->subscribe_cb != NULL) { + c->subscribe_cb(c, c->subscribe_cb_userdata); + } + + return FALSE; +} + +pa_operation *pa_ext_stream_restore2_write( + pa_context *c, gint mode, const pa_ext_stream_restore_info *data[], + unsigned n, int apply_immediately, pa_context_success_cb_t cb, + void *userdata) +{ + const pa_ext_stream_restore_info *info = data[0]; + + pa_cvolume_set(&c->volume, 1, info->volume.values[0]); + c->mute = info->mute; + + g_idle_add(_pa_ext_stream_restore_write_idle, c); + + return (gpointer) 0x1; +} + +static gboolean _pa_context_disconnect_idle(gpointer userdata) +{ + pa_context *c = userdata; + c->state = PA_CONTEXT_TERMINATED; + if (c->state_cb != NULL) { + c->state_cb(c, c->state_cb_userdata); + } + return FALSE; +} + +void pa_context_disconnect(pa_context *c) +{ + g_idle_add(_pa_context_disconnect_idle, c); +} + +pa_context *pa_context_get_instance(void) +{ + return context; +} diff --git a/tests/mafw-mock-pulseaudio.h b/tests/mafw-mock-pulseaudio.h new file mode 100644 index 0000000..bfb30a7 --- /dev/null +++ b/tests/mafw-mock-pulseaudio.h @@ -0,0 +1,33 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef MAFW_MOCK_PULSEAUDIO_H +#define MAFW_MOCK_PULSEAUDIO_H + +typedef struct _pa_context pa_context; + +void pa_context_disconnect(pa_context *c); +pa_context *pa_context_get_instance(void); + +#endif diff --git a/tests/mafw-test-player.c b/tests/mafw-test-player.c new file mode 100644 index 0000000..a340672 --- /dev/null +++ b/tests/mafw-test-player.c @@ -0,0 +1,312 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/* + * test.c + * + * Test of the playback system + * + * Copyright (C) 2007 Nokia Corporation + * + */ + +#include +#include +#include +#include +#include +#include + +#include "mafw-gst-renderer.h" +#include +#include + +MafwGstRenderer *gst_renderer; +GMainLoop *loop; +static struct termios tio_orig; +guint seek_delta = 2; +gfloat volume = 0.7; +gboolean muted = FALSE; + + +/** + *@time: how long to wait, in microsecs + * + */ +int kbhit (int time) { + fd_set rfds; + struct timeval tv; + int retval; + char c; + + FD_ZERO (&rfds); + FD_SET (0, &rfds); + + /* Wait up to 'time' microseconds. */ + tv.tv_sec=time / 1000; + tv.tv_usec = (time % 1000)*1000; + + retval=select (1, &rfds, NULL, NULL, &tv); + if(retval < 1) return -1; + retval = read (0, &c, 1); + if (retval < 1) return -1; + return (int) c; +} + + +/** + * + * + */ +static void raw_kb_enable (void) { + struct termios tio_new; + tcgetattr(0, &tio_orig); + + tio_new = tio_orig; + tio_new.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ + tio_new.c_cc[VMIN] = 1; + tio_new.c_cc[VTIME] = 0; + tcsetattr (0, TCSANOW, &tio_new); +} + +static void raw_kb_disable (void) +{ + tcsetattr (0,TCSANOW, &tio_orig); +} + +static void get_position_cb(MafwRenderer* self, gint position, gpointer user_data, + const GError* error) +{ + guint* rpos = (guint*) user_data; + g_return_if_fail(rpos != NULL); + + *rpos = position; +} + +/** + * + * + */ +static gboolean idle_cb (gpointer data) +{ + gboolean ret = TRUE; + GError *error = NULL; + + int c = kbhit (0); + if (c == -1) { + usleep (10 * 1000); + return TRUE; + } + + printf ("c = %d\n", c); + /* '.' key */ + if (c == 46) { + printf ("Seeking %d seconds forward\n", seek_delta); + gint pos = 0; + + mafw_gst_renderer_get_position(MAFW_RENDERER(gst_renderer), + get_position_cb, &pos); + + printf (" Position before seek: %d\n", pos); + pos += seek_delta; + mafw_gst_renderer_set_position(MAFW_RENDERER(gst_renderer), pos, + NULL, NULL); + + mafw_gst_renderer_get_position(MAFW_RENDERER(gst_renderer), + get_position_cb, &pos); + + printf (" Position after seek: %d\n", pos); + } + /* ',' key */ + else if (c == 44) { + printf ("Seeking %d seconds backwards\n", seek_delta); + gint pos = 0; + + mafw_gst_renderer_get_position(MAFW_RENDERER(gst_renderer), + get_position_cb, &pos); + + printf (" Position before seek: %d\n", pos); + pos -= seek_delta; + mafw_gst_renderer_set_position(MAFW_RENDERER(gst_renderer), pos, + NULL, NULL); + + mafw_gst_renderer_get_position(MAFW_RENDERER(gst_renderer), + get_position_cb, &pos); + + printf (" Position after seek: %d\n", pos); + } + /* '' (space) key */ + else if (c == 32) { + if (gst_renderer->current_state == Playing) { + printf ("Pausing...\n"); + mafw_gst_renderer_pause(MAFW_RENDERER (gst_renderer), NULL, NULL); + } + else if (gst_renderer->current_state == Paused) { + printf ("Resuming...\n"); + mafw_gst_renderer_resume(MAFW_RENDERER (gst_renderer), NULL, NULL); + } + } + /* 'p' key */ + else if (c == 112) { + printf ("Playing...\n"); + mafw_gst_renderer_play (MAFW_RENDERER (gst_renderer), NULL, NULL); + } + /* 's' key */ + else if (c == 115) { + printf ("Stopping\n"); + mafw_gst_renderer_stop (MAFW_RENDERER (gst_renderer), NULL, NULL); + } + /* 'g' key */ + else if (c == 103) { + printf ("Getting position\n"); + gint pos = 0; + + mafw_gst_renderer_get_position(MAFW_RENDERER(gst_renderer), + get_position_cb, &pos); + + printf ("Current position: %d\n", pos); + } + /* '+' key */ + else if (c == 43) { + volume += 0.1; + printf ("Increasing volume to %lf\n", volume); + mafw_extension_set_property_float(MAFW_EXTENSION(gst_renderer), + "volume", volume); + } + /* '-' key */ + else if (c == 45) { + volume -= 0.1; + printf ("Decreasing volume to %lf\n", volume); + mafw_extension_set_property_float(MAFW_EXTENSION(gst_renderer), + "volume", volume); + } + /* 'm' key */ + else if (c == 109) { + muted = !muted; + printf ("(Un)Muting...\n"); + mafw_extension_set_property_boolean(MAFW_EXTENSION(gst_renderer), + "mute", muted); + } + /* '?' key */ + else if (c == 63) { + printf ("COMMANDS:\n" \ + " s\t\tStop\n" \ + " p\t\tPlay\n" \ + " space\tPause/Resume\n" \ + " +\t\tVolume up\n" \ + " -\t\tVolume down\n" \ + " m\t\tMute/Unmute\n" \ + " .\t\tSeek forward 2 sec\n" \ + " ,\t\tSeek backwards 2 sec\n" \ + " q\t\tQuit\n"); + } + /* 'q' key */ + else if (c == 113) { + printf ("QUIT\n"); + mafw_gst_renderer_stop (MAFW_RENDERER (gst_renderer), NULL, NULL); + raw_kb_disable (); + g_main_loop_quit (loop); + ret = FALSE; + } + if (error) { + printf ("Error occured during the operation\n"); + g_error_free (error); + } + return ret; +} + + +/** + * + * + */ +static void metadata_changed (MafwGstRenderer *gst_renderer, + GHashTable *metadata, + gpointer user_data) +{ + g_print("Metadata changed:\n"); + mafw_metadata_print (metadata, NULL); +} + + +/** + * + * + */ +static void buffering_cb (MafwGstRenderer *gst_renderer, + gfloat percentage, + gpointer user_data) +{ + g_print("Buffering: %f\n", percentage); +} + +static void play_uri_cb(MafwRenderer* renderer, gpointer user_data, const GError* error) +{ + if (error != NULL) { + printf("Unable to play: %s\n", error->message); + exit(1); + } +} + +/** + * + * + */ +gint main(gint argc, gchar ** argv) +{ + MafwRegistry *registry; + + g_type_init(); + gst_init (&argc, &argv); + + if (argc != 2) { + g_print("Usage: mafw-test-player \n"); + exit(1); + } + + raw_kb_enable(); + + registry = MAFW_REGISTRY(mafw_registry_get_instance()); + gst_renderer = MAFW_GST_RENDERER(mafw_gst_renderer_new(registry)); + g_signal_connect (G_OBJECT (gst_renderer), + "metadata_changed", + G_CALLBACK (metadata_changed), + gst_renderer); + + g_signal_connect (G_OBJECT (gst_renderer), + "buffering_info", + G_CALLBACK (buffering_cb), + gst_renderer); + + mafw_renderer_play_uri(MAFW_RENDERER (gst_renderer), argv[1], play_uri_cb, + NULL); + + loop = mafw_gst_renderer_get_loop(gst_renderer); + + g_idle_add (idle_cb, NULL); + g_main_loop_run (loop); + + g_object_unref (G_OBJECT (gst_renderer)); + return 0; +} diff --git a/tests/media/test.avi b/tests/media/test.avi new file mode 100644 index 0000000000000000000000000000000000000000..f868c625c4bd780ab3e33dc5cfb83ee6a2b28049 GIT binary patch literal 86540 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%0Aun=rc|E*dFqHw3+5l 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 U+!+ndl6(7R+YIF5J-HN$3U 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 ZImh{7HFeJ+Z3dZ_$Zv+(pL7z;{{gZ!UmE}b literal 0 HcmV?d00001 diff --git a/tests/media/testframe.png b/tests/media/testframe.png new file mode 100644 index 0000000000000000000000000000000000000000..a0db335ffcc6a396393e883b44ab5aacb41c2f36 GIT binary patch literal 13945 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 OnWC(!OoNnJ`2Pcz^d~C- literal 0 HcmV?d00001 diff --git a/tests/test.suppressions b/tests/test.suppressions new file mode 100644 index 0000000..8d0b36b --- /dev/null +++ b/tests/test.suppressions @@ -0,0 +1,864 @@ +{ + <1> + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_type_create_instance + fun:g_object_constructor + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* + obj:* + obj:* + fun:gst_element_change_state + fun:gst_element_continue_state + fun:gst_element_change_state + obj:* + fun:gst_element_set_state + fun:_construct_pipeline +} +{ + <2> + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_type_create_instance + fun:g_object_constructor + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_pad_new_from_template + obj:* + fun:g_type_create_instance + fun:g_object_constructor + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <3> + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_error + fun:dlerror_run + fun:__libc_dlopen_mode + fun:__nss_lookup_function + fun:__nss_lookup + fun:__nss_passwd_lookup + fun:getpwnam_r@@GLIBC_2.1.2 +} +{ + <4> + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:* + fun:g_signal_newv + fun:g_signal_new_valist + fun:g_signal_new + obj:* + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new +} + +{ + <5> + Memcheck:Leak + fun:calloc + fun:g_malloc0 + obj:* + fun:g_type_create_instance + obj:* + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* + obj:* +} + +{ + <6> + Memcheck:Leak + fun:vasprintf + fun:g_vasprintf + fun:g_strdup_vprintf + fun:g_strdup_printf + fun:gst_uri_construct + fun:g_object_newv + obj:* + obj:* + fun:gst_uri_handler_set_uri + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <7> + Memcheck:Leak + fun:malloc + fun:open_path + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run +} +{ + <8> + Memcheck:Leak + fun:malloc + fun:expand_dynamic_string_token + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file +} +{ + <9> + Memcheck:Leak + fun:malloc + fun:_dl_new_object + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open +} +{ + <10> + Memcheck:Leak + fun:malloc + fun:_dl_map_object_deps + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name +} +{ + <11> + Memcheck:Leak + fun:calloc + fun:_dl_check_map_versions + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name +} +{ + <12> + Memcheck:Leak + fun:malloc + fun:g_malloc0 + obj:* + obj:* + fun:g_type_create_instance + obj:* + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <13> + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + obj:* + fun:g_array_sized_new + obj:* + fun:gst_structure_copy + fun:gst_caps_copy + fun:gst_audio_filter_class_add_pad_templates + obj:* + fun:g_type_class_ref + fun:gst_element_register +} +{ + <14> + Memcheck:Cond + fun:strlen + fun:_dl_init_paths + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start +} +{ + <15> + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start +} +{ + <15> + Memcheck:Leak + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.1 + obj:* + fun:g_thread_create_full + obj:* + fun:g_thread_pool_push + fun:gst_task_start + fun:gst_pad_start_task + obj:* + fun:gst_pad_activate_pull +} +{ + <16> + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name + fun:gst_plugin_feature_load +} +{ + <17> + Memcheck:Leak + fun:malloc + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name + fun:gst_plugin_feature_load +} +{ + <17> + Memcheck:Leak + fun:realloc + fun:vasprintf + fun:g_vasprintf + fun:g_strdup_vprintf + fun:g_strdup_printf + fun:gst_uri_construct + fun:gst_file_src_set_location + fun:gst_file_src_uri_set_uri + fun:gst_uri_handler_set_uri + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <18> + Memcheck:Leak + fun:calloc + fun:parse_bracket_exp + fun:parse_expression + fun:parse_branch + fun:parse_reg_exp + fun:parse_expression + fun:parse_branch + fun:parse_reg_exp + fun:parse_expression + fun:parse_branch + fun:parse_reg_exp + fun:re_compile_internal +} +{ + <19> + Memcheck:Leak + fun:malloc + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name +} +{ + <20> + Memcheck:Leak + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open +} +{ + <21> + Memcheck:Leak + fun:malloc + fun:g_malloc + obj:* + obj:* + fun:g_type_create_instance + obj:* + fun:g_object_newv + fun:g_object_new_valist + fun:g_object_new + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* +} +{ + <22> + Memcheck:Leak + fun:calloc + fun:dbus_malloc0 + obj:* + obj:* + obj:* + obj:* + fun:dbus_parse_address + obj:* + fun:dbus_connection_open + obj:* + fun:dbus_bus_get + obj:* +} +{ + <23> + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:* + fun:g_array_append_vals + obj:* + fun:gst_structure_set_valist + fun:gst_caps_set_simple + fun:gst_riff_create_audio_caps + fun:gst_riff_create_audio_template_caps + obj:* + fun:g_type_class_ref + fun:gst_element_register +} +{ + <24> + Memcheck:Leak + fun:realloc + fun:dbus_realloc + obj:* + obj:* + obj:* + obj:* + obj:* + obj:* + obj:* + obj:* + obj:* + obj:* +} +{ + <25> + Memcheck:Leak + fun:calloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open +} +{ + <26> + Memcheck:Leak + fun:realloc + fun:vasprintf + fun:g_vasprintf + fun:g_strdup_vprintf + fun:g_strdup_printf + fun:gst_uri_construct + obj:* + obj:* + fun:gst_uri_handler_set_uri + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <27> + Memcheck:Leak + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.1 + fun:g_module_open +} +{ + <28> + Memcheck:Leak + fun:realloc + fun:vasprintf + fun:g_vasprintf + fun:g_strdup_vprintf + fun:g_strdup_printf + fun:gst_uri_construct + obj:* + obj:* + fun:gst_uri_handler_set_uri + fun:gst_element_make_from_uri + obj:* + obj:* +} +{ + <29> + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + fun:gst_object_set_name + fun:gst_element_factory_create + fun:gst_element_make_from_uri + obj:* + obj:* + obj:* + fun:gst_element_change_state + fun:gst_element_continue_state + fun:gst_element_change_state +} +{ + <30> + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:* + obj:* + obj:* + obj:* +} +{ + <31> + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so +} +{ + <32> + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/targets/*/lib/libc-2.5.so + fun:__nss_passwd_lookup + fun:getpwnam_r + obj:/targets/*/usr/lib/libglib-2.0.so.0.1800.1 +} +{ + <33> + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/targets/*/lib/libc-2.5.so + fun:__nss_passwd_lookup + fun:getpwnam_r + obj:/targets/*/usr/lib/libglib-2.0.so.0.1800.1 +} +{ + <34> + Memcheck:Leak + fun:calloc + fun:g_malloc0 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.1800.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.1800.1 + fun:g_type_init_with_debug_flags + fun:g_type_init + fun:fx_setup_dummy_gst_renderer + fun:tcase_run_checked_setup + fun:srunner_run_all + fun:main +} +{ + <35> + Memcheck:Leak + fun:calloc + fun:g_malloc0 + obj:/targets/*/usr/lib/libglib-2.0.so.0.1800.1 + fun:g_slice_alloc + fun:g_slist_prepend + fun:g_strsplit + fun:mafw_log_init + fun:configure_tests + fun:main +} +{ + <36> + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so +} +{ + <37> + Memcheck:Leak + fun:* + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:reg* + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all + fun:main +} +{ + <38> + Memcheck:Leak + fun:* + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so +} +{ + <39> + Memcheck:Leak + fun:* + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:reg* + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all + fun:main +} +{ + <40> + Memcheck:Leak + fun:* + obj:/targets/*/lib/libc-2.5.so + fun:reg* + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all + fun:main +} +{ + <41> + Memcheck:Leak + fun:* + fun:reg* + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all + fun:main +} +{ + <42> + Memcheck:Leak + fun:realloc + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:regcomp + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all +} +{ + <43> + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/targets/*/lib/libc-2.5.so + fun:__nss_passwd_lookup + fun:getpwnam_r +} +{ + <44> + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/targets/*/lib/libc-2.5.so +} +{ + <45> + Memcheck:Leak + fun:malloc + fun:fdopen + fun:tmpfile + fun:setup_pipe + fun:srunner_run_all + fun:main +} +{ + <46> + Memcheck:Leak + fun:realloc + fun:erealloc + fun:maybe_grow + fun:list_add_end + fun:_tcase_add_test + fun:configure_tests + fun:main +} +{ + <47> + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.1800.1 + fun:g_type_register_static + fun:g_type_plugin_get_type + fun:g_type_init_with_debug_flags + fun:g_type_init + fun:fx_setup_dummy_gst_renderer + fun:tcase_run_checked_setup + fun:srunner_run_all + fun:main +} +{ + <48> + Memcheck:Leak + fun:* + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:reg* + fun:mafw_source_create_objectid + fun:get_sample_clip_objectid + fun:* + fun:srunner_run_all + fun:main +} +{ + <49> + Memcheck:Leak + fun:malloc + fun:emalloc + fun:suite_create + fun:configure_tests + fun:main +} +{ + <50> + Memcheck:Leak + fun:malloc + fun:dbus_malloc + obj:/targets/*/usr/lib/libdbus-1.so.3.4.0 + obj:/targets/*/usr/lib/libdbus-1.so.3.4.0 + fun:dbus_bus_get + obj:/targets/*/usr/lib/libosso.so.1.3.0 + fun:osso_initialize + fun:blanking_init + fun:mafw_gst_renderer_worker_new + fun:mafw_gst_renderer_init + fun:g_type_create_instance + obj:/targets/*/usr/lib/libgobject-2.0.so.0.1800.1 +} +{ + <51> + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.18.0 + fun:gst_registry_binary_read_cache + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.18.0 + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.18.0 + fun:g_option_context_parse + fun:gst_init_check + fun:gst_init + fun:mafw_gst_renderer_class_init + fun:mafw_gst_renderer_class_intern_init +} +{ + <52> + Memcheck:Leak + fun:* + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:g_module_open +} +{ + <53> + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_hash_table_new_full + fun:g_hash_table_new + fun:g_quark_from_static_string + fun:g_type_init_with_debug_flags + fun:g_type_init + fun:fx_setup_dummy_gst_renderer + fun:tcase_run_checked_setup + fun:srunner_run_all + fun:main +} +{ + <54> + Memcheck:Leak + fun:* + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so +} +{ + <55> + Memcheck:Leak + fun:malloc + fun:fdopen + fun:tmpfile + fun:setup_pipe + fun:receive_test_result + fun:srunner_run_all + fun:main +} diff --git a/welcome b/welcome deleted file mode 100644 index e69de29..0000000 -- 1.7.9.5