From 08f86cf0cc0cd1e3ac8787225d8b038c6eedeb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Wed, 27 Jan 2021 14:44:09 +0100 Subject: [PATCH 01/19] Include "GstPlayer" lib renamed to "GstClapper" as part of the app --- lib/gst/COPYING | 502 ++ lib/gst/clapper/clapper-prelude.h | 42 + lib/gst/clapper/clapper.h | 32 + ...clapper-g-main-context-signal-dispatcher.c | 214 + ...clapper-g-main-context-signal-dispatcher.h | 51 + .../clapper/gstclapper-media-info-private.h | 123 + lib/gst/clapper/gstclapper-media-info.c | 856 +++ lib/gst/clapper/gstclapper-media-info.h | 247 + .../gstclapper-signal-dispatcher-private.h | 35 + .../clapper/gstclapper-signal-dispatcher.c | 58 + .../clapper/gstclapper-signal-dispatcher.h | 53 + lib/gst/clapper/gstclapper-types.h | 37 + .../gstclapper-video-overlay-video-renderer.c | 345 ++ .../gstclapper-video-overlay-video-renderer.h | 71 + .../gstclapper-video-renderer-private.h | 33 + lib/gst/clapper/gstclapper-video-renderer.c | 50 + lib/gst/clapper/gstclapper-video-renderer.h | 49 + lib/gst/clapper/gstclapper-visualization.c | 180 + lib/gst/clapper/gstclapper-visualization.h | 61 + lib/gst/clapper/gstclapper.c | 4789 +++++++++++++++++ lib/gst/clapper/gstclapper.h | 299 + lib/gst/clapper/meson.build | 51 + lib/gst/meson.build | 1 + lib/meson.build | 237 + meson.build | 26 +- meson_options.txt | 20 + 26 files changed, 8452 insertions(+), 10 deletions(-) create mode 100644 lib/gst/COPYING create mode 100644 lib/gst/clapper/clapper-prelude.h create mode 100644 lib/gst/clapper/clapper.h create mode 100644 lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.c create mode 100644 lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.h create mode 100644 lib/gst/clapper/gstclapper-media-info-private.h create mode 100644 lib/gst/clapper/gstclapper-media-info.c create mode 100644 lib/gst/clapper/gstclapper-media-info.h create mode 100644 lib/gst/clapper/gstclapper-signal-dispatcher-private.h create mode 100644 lib/gst/clapper/gstclapper-signal-dispatcher.c create mode 100644 lib/gst/clapper/gstclapper-signal-dispatcher.h create mode 100644 lib/gst/clapper/gstclapper-types.h create mode 100644 lib/gst/clapper/gstclapper-video-overlay-video-renderer.c create mode 100644 lib/gst/clapper/gstclapper-video-overlay-video-renderer.h create mode 100644 lib/gst/clapper/gstclapper-video-renderer-private.h create mode 100644 lib/gst/clapper/gstclapper-video-renderer.c create mode 100644 lib/gst/clapper/gstclapper-video-renderer.h create mode 100644 lib/gst/clapper/gstclapper-visualization.c create mode 100644 lib/gst/clapper/gstclapper-visualization.h create mode 100644 lib/gst/clapper/gstclapper.c create mode 100644 lib/gst/clapper/gstclapper.h create mode 100644 lib/gst/clapper/meson.build create mode 100644 lib/gst/meson.build create mode 100644 lib/meson.build create mode 100644 meson_options.txt diff --git a/lib/gst/COPYING b/lib/gst/COPYING new file mode 100644 index 00000000..4362b491 --- /dev/null +++ b/lib/gst/COPYING @@ -0,0 +1,502 @@ + 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/lib/gst/clapper/clapper-prelude.h b/lib/gst/clapper/clapper-prelude.h new file mode 100644 index 00000000..56380aa4 --- /dev/null +++ b/lib/gst/clapper/clapper-prelude.h @@ -0,0 +1,42 @@ +/* GStreamer + * Copyright (C) 2018 GStreamer developers + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_PRELUDE_H__ +#define __GST_CLAPPER_PRELUDE_H__ + +#include + +#ifndef GST_CLAPPER_API +# ifdef BUILDING_GST_CLAPPER +# define GST_CLAPPER_API GST_API_EXPORT /* from config.h */ +# else +# define GST_CLAPPER_API GST_API_IMPORT +# endif +#endif + +#ifndef GST_DISABLE_DEPRECATED +#define GST_CLAPPER_DEPRECATED GST_CLAPPER_API +#define GST_CLAPPER_DEPRECATED_FOR(f) GST_CLAPPER_API +#else +#define GST_CLAPPER_DEPRECATED G_DEPRECATED GST_CLAPPER_API +#define GST_CLAPPER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) GST_CLAPPER_API +#endif + +#endif /* __GST_CLAPPER_PRELUDE_H__ */ diff --git a/lib/gst/clapper/clapper.h b/lib/gst/clapper/clapper.h new file mode 100644 index 00000000..d81739f4 --- /dev/null +++ b/lib/gst/clapper/clapper.h @@ -0,0 +1,32 @@ +/* GStreamer + * + * Copyright (C) 2014 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __CLAPPER_H__ +#define __CLAPPER_H__ + +#include +#include +#include +#include +#include +#include + +#endif /* __CLAPPER_H__ */ diff --git a/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.c b/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.c new file mode 100644 index 00000000..f2e8419b --- /dev/null +++ b/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.c @@ -0,0 +1,214 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-gmaincontextsignaldispatcher + * @title: GstClapperGMainContextSignalDispatcher + * @short_description: Clapper GLib MainContext dispatcher + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-g-main-context-signal-dispatcher.h" + +struct _GstClapperGMainContextSignalDispatcher +{ + GObject parent; + GMainContext *application_context; +}; + +struct _GstClapperGMainContextSignalDispatcherClass +{ + GObjectClass parent_class; +}; + +static void + gst_clapper_g_main_context_signal_dispatcher_interface_init + (GstClapperSignalDispatcherInterface * iface); + +enum +{ + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_0, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstClapperGMainContextSignalDispatcher, + gst_clapper_g_main_context_signal_dispatcher, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_CLAPPER_SIGNAL_DISPATCHER, + gst_clapper_g_main_context_signal_dispatcher_interface_init)); + +static GParamSpec + * g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; + +static void +gst_clapper_g_main_context_signal_dispatcher_finalize (GObject * object) +{ + GstClapperGMainContextSignalDispatcher *self = + GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + if (self->application_context) + g_main_context_unref (self->application_context); + + G_OBJECT_CLASS + (gst_clapper_g_main_context_signal_dispatcher_parent_class)->finalize + (object); +} + +static void +gst_clapper_g_main_context_signal_dispatcher_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstClapperGMainContextSignalDispatcher *self = + GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + self->application_context = g_value_dup_boxed (value); + if (!self->application_context) + self->application_context = g_main_context_ref_thread_default (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_g_main_context_signal_dispatcher_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstClapperGMainContextSignalDispatcher *self = + GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + g_value_set_boxed (value, self->application_context); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void + gst_clapper_g_main_context_signal_dispatcher_class_init + (GstClapperGMainContextSignalDispatcherClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = + gst_clapper_g_main_context_signal_dispatcher_finalize; + gobject_class->set_property = + gst_clapper_g_main_context_signal_dispatcher_set_property; + gobject_class->get_property = + gst_clapper_g_main_context_signal_dispatcher_get_property; + + g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT] = + g_param_spec_boxed ("application-context", "Application Context", + "Application GMainContext to dispatch signals to", G_TYPE_MAIN_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST, + g_main_context_signal_dispatcher_param_specs); +} + +static void + gst_clapper_g_main_context_signal_dispatcher_init + (G_GNUC_UNUSED GstClapperGMainContextSignalDispatcher * self) +{ +} + +typedef struct +{ + void (*emitter) (gpointer data); + gpointer data; + GDestroyNotify destroy; +} GMainContextSignalDispatcherData; + +static gboolean +g_main_context_signal_dispatcher_dispatch_gsourcefunc (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + data->emitter (data->data); + + return G_SOURCE_REMOVE; +} + +static void +g_main_context_signal_dispatcher_dispatch_destroy (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + if (data->destroy) + data->destroy (data->data); + g_free (data); +} + +static void +gst_clapper_g_main_context_signal_dispatcher_dispatch (GstClapperSignalDispatcher + * iface, G_GNUC_UNUSED GstClapper * clapper, void (*emitter) (gpointer data), + gpointer data, GDestroyNotify destroy) +{ + GstClapperGMainContextSignalDispatcher *self = + GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (iface); + GMainContextSignalDispatcherData *gsourcefunc_data = + g_new (GMainContextSignalDispatcherData, 1); + + gsourcefunc_data->emitter = emitter; + gsourcefunc_data->data = data; + gsourcefunc_data->destroy = destroy; + + g_main_context_invoke_full (self->application_context, + G_PRIORITY_DEFAULT, g_main_context_signal_dispatcher_dispatch_gsourcefunc, + gsourcefunc_data, g_main_context_signal_dispatcher_dispatch_destroy); +} + +static void + gst_clapper_g_main_context_signal_dispatcher_interface_init + (GstClapperSignalDispatcherInterface * iface) +{ + iface->dispatch = gst_clapper_g_main_context_signal_dispatcher_dispatch; +} + +/** + * gst_clapper_g_main_context_signal_dispatcher_new: + * @application_context: (allow-none): GMainContext to use or %NULL + * + * Creates a new GstClapperSignalDispatcher that uses @application_context, + * or the thread default one if %NULL is used. See gst_clapper_new(). + * + * Returns: (transfer full): the new GstClapperSignalDispatcher + */ +GstClapperSignalDispatcher * +gst_clapper_g_main_context_signal_dispatcher_new (GMainContext * + application_context) +{ + return g_object_new (GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, + "application-context", application_context, NULL); +} diff --git a/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.h b/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.h new file mode 100644 index 00000000..a423b2d0 --- /dev/null +++ b/lib/gst/clapper/gstclapper-g-main-context-signal-dispatcher.h @@ -0,0 +1,51 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ +#define __GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperGMainContextSignalDispatcher + GstClapperGMainContextSignalDispatcher; +typedef struct _GstClapperGMainContextSignalDispatcherClass + GstClapperGMainContextSignalDispatcherClass; + +#define GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (gst_clapper_g_main_context_signal_dispatcher_get_type ()) +#define GST_IS_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_IS_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstClapperGMainContextSignalDispatcherClass)) +#define GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstClapperGMainContextSignalDispatcher)) +#define GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstClapperGMainContextSignalDispatcherClass)) +#define GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST(obj) ((GstClapperGMainContextSignalDispatcher*)(obj)) + +GST_CLAPPER_API +GType gst_clapper_g_main_context_signal_dispatcher_get_type (void); + +GST_CLAPPER_API +GstClapperSignalDispatcher * gst_clapper_g_main_context_signal_dispatcher_new (GMainContext * application_context); + +G_END_DECLS + +#endif /* __GST_CLAPPER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_H__ */ diff --git a/lib/gst/clapper/gstclapper-media-info-private.h b/lib/gst/clapper/gstclapper-media-info-private.h new file mode 100644 index 00000000..00f148b3 --- /dev/null +++ b/lib/gst/clapper/gstclapper-media-info-private.h @@ -0,0 +1,123 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstclapper-media-info.h" + +#ifndef __GST_CLAPPER_MEDIA_INFO_PRIVATE_H__ +#define __GST_CLAPPER_MEDIA_INFO_PRIVATE_H__ + +struct _GstClapperStreamInfo +{ + GObject parent; + + gchar *codec; + + GstCaps *caps; + gint stream_index; + GstTagList *tags; + gchar *stream_id; +}; + +struct _GstClapperStreamInfoClass +{ + GObjectClass parent_class; +}; + +struct _GstClapperSubtitleInfo +{ + GstClapperStreamInfo parent; + + gchar *language; +}; + +struct _GstClapperSubtitleInfoClass +{ + GstClapperStreamInfoClass parent_class; +}; + +struct _GstClapperAudioInfo +{ + GstClapperStreamInfo parent; + + gint channels; + gint sample_rate; + + guint bitrate; + guint max_bitrate; + + gchar *language; +}; + +struct _GstClapperAudioInfoClass +{ + GstClapperStreamInfoClass parent_class; +}; + +struct _GstClapperVideoInfo +{ + GstClapperStreamInfo parent; + + gint width; + gint height; + gint framerate_num; + gint framerate_denom; + gint par_num; + gint par_denom; + + guint bitrate; + guint max_bitrate; +}; + +struct _GstClapperVideoInfoClass +{ + GstClapperStreamInfoClass parent_class; +}; + +struct _GstClapperMediaInfo +{ + GObject parent; + + gchar *uri; + gchar *title; + gchar *container; + gboolean seekable, is_live; + GstTagList *tags; + GstSample *image_sample; + + GList *stream_list; + GList *audio_stream_list; + GList *video_stream_list; + GList *subtitle_stream_list; + + GstClockTime duration; +}; + +struct _GstClapperMediaInfoClass +{ + GObjectClass parent_class; +}; + +G_GNUC_INTERNAL GstClapperMediaInfo * gst_clapper_media_info_new (const gchar *uri); +G_GNUC_INTERNAL GstClapperMediaInfo * gst_clapper_media_info_copy (GstClapperMediaInfo *ref); +G_GNUC_INTERNAL GstClapperStreamInfo * gst_clapper_stream_info_new (gint stream_index, GType type); +G_GNUC_INTERNAL GstClapperStreamInfo * gst_clapper_stream_info_copy (GstClapperStreamInfo *ref); + +#endif /* __GST_CLAPPER_MEDIA_INFO_PRIVATE_H__ */ diff --git a/lib/gst/clapper/gstclapper-media-info.c b/lib/gst/clapper/gstclapper-media-info.c new file mode 100644 index 00000000..6a2ffe23 --- /dev/null +++ b/lib/gst/clapper/gstclapper-media-info.c @@ -0,0 +1,856 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-mediainfo + * @title: GstClapperMediaInfo + * @short_description: Clapper Media Information + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-media-info.h" +#include "gstclapper-media-info-private.h" + +/* Per-stream information */ +G_DEFINE_ABSTRACT_TYPE (GstClapperStreamInfo, gst_clapper_stream_info, + G_TYPE_OBJECT); + +static void +gst_clapper_stream_info_init (GstClapperStreamInfo * sinfo) +{ + sinfo->stream_index = -1; +} + +static void +gst_clapper_stream_info_finalize (GObject * object) +{ + GstClapperStreamInfo *sinfo = GST_CLAPPER_STREAM_INFO (object); + + g_free (sinfo->codec); + g_free (sinfo->stream_id); + + if (sinfo->caps) + gst_caps_unref (sinfo->caps); + + if (sinfo->tags) + gst_tag_list_unref (sinfo->tags); + + G_OBJECT_CLASS (gst_clapper_stream_info_parent_class)->finalize (object); +} + +static void +gst_clapper_stream_info_class_init (GstClapperStreamInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_clapper_stream_info_finalize; +} + +/** + * gst_clapper_stream_info_get_index: + * @info: a #GstClapperStreamInfo + * + * Function to get stream index from #GstClapperStreamInfo instance. + * + * Returns: the stream index of this stream. + */ +gint +gst_clapper_stream_info_get_index (const GstClapperStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_STREAM_INFO (info), -1); + + return info->stream_index; +} + +/** + * gst_clapper_stream_info_get_stream_type: + * @info: a #GstClapperStreamInfo + * + * Function to return human readable name for the stream type + * of the given @info (ex: "audio", "video", "subtitle") + * + * Returns: a human readable name + */ +const gchar * +gst_clapper_stream_info_get_stream_type (const GstClapperStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_STREAM_INFO (info), NULL); + + if (GST_IS_CLAPPER_VIDEO_INFO (info)) + return "video"; + else if (GST_IS_CLAPPER_AUDIO_INFO (info)) + return "audio"; + else + return "subtitle"; +} + +/** + * gst_clapper_stream_info_get_tags: + * @info: a #GstClapperStreamInfo + * + * Returns: (transfer none): the tags contained in this stream. + */ +GstTagList * +gst_clapper_stream_info_get_tags (const GstClapperStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_STREAM_INFO (info), NULL); + + return info->tags; +} + +/** + * gst_clapper_stream_info_get_codec: + * @info: a #GstClapperStreamInfo + * + * A string describing codec used in #GstClapperStreamInfo. + * + * Returns: codec string or NULL on unknown. + */ +const gchar * +gst_clapper_stream_info_get_codec (const GstClapperStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_STREAM_INFO (info), NULL); + + return info->codec; +} + +/** + * gst_clapper_stream_info_get_caps: + * @info: a #GstClapperStreamInfo + * + * Returns: (transfer none): the #GstCaps of the stream. + */ +GstCaps * +gst_clapper_stream_info_get_caps (const GstClapperStreamInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_STREAM_INFO (info), NULL); + + return info->caps; +} + +/* Video information */ +G_DEFINE_TYPE (GstClapperVideoInfo, gst_clapper_video_info, + GST_TYPE_CLAPPER_STREAM_INFO); + +static void +gst_clapper_video_info_init (GstClapperVideoInfo * info) +{ + info->width = -1; + info->height = -1; + info->framerate_num = 0; + info->framerate_denom = 1; + info->par_num = 1; + info->par_denom = 1; +} + +static void +gst_clapper_video_info_class_init (G_GNUC_UNUSED GstClapperVideoInfoClass * klass) +{ + /* nothing to do here */ +} + +/** + * gst_clapper_video_info_get_width: + * @info: a #GstClapperVideoInfo + * + * Returns: the width of video in #GstClapperVideoInfo. + */ +gint +gst_clapper_video_info_get_width (const GstClapperVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info), -1); + + return info->width; +} + +/** + * gst_clapper_video_info_get_height: + * @info: a #GstClapperVideoInfo + * + * Returns: the height of video in #GstClapperVideoInfo. + */ +gint +gst_clapper_video_info_get_height (const GstClapperVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info), -1); + + return info->height; +} + +/** + * gst_clapper_video_info_get_framerate: + * @info: a #GstClapperVideoInfo + * @fps_n: (out): Numerator of frame rate + * @fps_d: (out): Denominator of frame rate + * + */ +void +gst_clapper_video_info_get_framerate (const GstClapperVideoInfo * info, + gint * fps_n, gint * fps_d) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info)); + + *fps_n = info->framerate_num; + *fps_d = info->framerate_denom; +} + +/** + * gst_clapper_video_info_get_pixel_aspect_ratio: + * @info: a #GstClapperVideoInfo + * @par_n: (out): numerator + * @par_d: (out): denominator + * + * Returns the pixel aspect ratio in @par_n and @par_d + * + */ +void +gst_clapper_video_info_get_pixel_aspect_ratio (const GstClapperVideoInfo * info, + guint * par_n, guint * par_d) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info)); + + *par_n = info->par_num; + *par_d = info->par_denom; +} + +/** + * gst_clapper_video_info_get_bitrate: + * @info: a #GstClapperVideoInfo + * + * Returns: the current bitrate of video in #GstClapperVideoInfo. + */ +gint +gst_clapper_video_info_get_bitrate (const GstClapperVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info), -1); + + return info->bitrate; +} + +/** + * gst_clapper_video_info_get_max_bitrate: + * @info: a #GstClapperVideoInfo + * + * Returns: the maximum bitrate of video in #GstClapperVideoInfo. + */ +gint +gst_clapper_video_info_get_max_bitrate (const GstClapperVideoInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_INFO (info), -1); + + return info->max_bitrate; +} + +/* Audio information */ +G_DEFINE_TYPE (GstClapperAudioInfo, gst_clapper_audio_info, + GST_TYPE_CLAPPER_STREAM_INFO); + +static void +gst_clapper_audio_info_init (GstClapperAudioInfo * info) +{ + info->channels = 0; + info->sample_rate = 0; + info->bitrate = -1; + info->max_bitrate = -1; +} + +static void +gst_clapper_audio_info_finalize (GObject * object) +{ + GstClapperAudioInfo *info = GST_CLAPPER_AUDIO_INFO (object); + + g_free (info->language); + + G_OBJECT_CLASS (gst_clapper_audio_info_parent_class)->finalize (object); +} + +static void +gst_clapper_audio_info_class_init (GstClapperAudioInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_clapper_audio_info_finalize; +} + +/** + * gst_clapper_audio_info_get_language: + * @info: a #GstClapperAudioInfo + * + * Returns: the language of the stream, or NULL if unknown. + */ +const gchar * +gst_clapper_audio_info_get_language (const GstClapperAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_AUDIO_INFO (info), NULL); + + return info->language; +} + +/** + * gst_clapper_audio_info_get_channels: + * @info: a #GstClapperAudioInfo + * + * Returns: the number of audio channels in #GstClapperAudioInfo. + */ +gint +gst_clapper_audio_info_get_channels (const GstClapperAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_AUDIO_INFO (info), 0); + + return info->channels; +} + +/** + * gst_clapper_audio_info_get_sample_rate: + * @info: a #GstClapperAudioInfo + * + * Returns: the audio sample rate in #GstClapperAudioInfo. + */ +gint +gst_clapper_audio_info_get_sample_rate (const GstClapperAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_AUDIO_INFO (info), 0); + + return info->sample_rate; +} + +/** + * gst_clapper_audio_info_get_bitrate: + * @info: a #GstClapperAudioInfo + * + * Returns: the audio bitrate in #GstClapperAudioInfo. + */ +gint +gst_clapper_audio_info_get_bitrate (const GstClapperAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_AUDIO_INFO (info), -1); + + return info->bitrate; +} + +/** + * gst_clapper_audio_info_get_max_bitrate: + * @info: a #GstClapperAudioInfo + * + * Returns: the audio maximum bitrate in #GstClapperAudioInfo. + */ +gint +gst_clapper_audio_info_get_max_bitrate (const GstClapperAudioInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_AUDIO_INFO (info), -1); + + return info->max_bitrate; +} + +/* Subtitle information */ +G_DEFINE_TYPE (GstClapperSubtitleInfo, gst_clapper_subtitle_info, + GST_TYPE_CLAPPER_STREAM_INFO); + +static void +gst_clapper_subtitle_info_init (G_GNUC_UNUSED GstClapperSubtitleInfo * info) +{ + /* nothing to do */ +} + +static void +gst_clapper_subtitle_info_finalize (GObject * object) +{ + GstClapperSubtitleInfo *info = GST_CLAPPER_SUBTITLE_INFO (object); + + g_free (info->language); + + G_OBJECT_CLASS (gst_clapper_subtitle_info_parent_class)->finalize (object); +} + +static void +gst_clapper_subtitle_info_class_init (GstClapperSubtitleInfoClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_clapper_subtitle_info_finalize; +} + +/** + * gst_clapper_subtitle_info_get_language: + * @info: a #GstClapperSubtitleInfo + * + * Returns: the language of the stream, or NULL if unknown. + */ +const gchar * +gst_clapper_subtitle_info_get_language (const GstClapperSubtitleInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_SUBTITLE_INFO (info), NULL); + + return info->language; +} + +/* Global media information */ +G_DEFINE_TYPE (GstClapperMediaInfo, gst_clapper_media_info, G_TYPE_OBJECT); + +static void +gst_clapper_media_info_init (GstClapperMediaInfo * info) +{ + info->duration = -1; + info->is_live = FALSE; + info->seekable = FALSE; +} + +static void +gst_clapper_media_info_finalize (GObject * object) +{ + GstClapperMediaInfo *info = GST_CLAPPER_MEDIA_INFO (object); + + g_free (info->uri); + + if (info->tags) + gst_tag_list_unref (info->tags); + + g_free (info->title); + + g_free (info->container); + + if (info->image_sample) + gst_sample_unref (info->image_sample); + + if (info->audio_stream_list) + g_list_free (info->audio_stream_list); + + if (info->video_stream_list) + g_list_free (info->video_stream_list); + + if (info->subtitle_stream_list) + g_list_free (info->subtitle_stream_list); + + if (info->stream_list) + g_list_free_full (info->stream_list, g_object_unref); + + G_OBJECT_CLASS (gst_clapper_media_info_parent_class)->finalize (object); +} + +static void +gst_clapper_media_info_class_init (GstClapperMediaInfoClass * klass) +{ + GObjectClass *oclass = (GObjectClass *) klass; + + oclass->finalize = gst_clapper_media_info_finalize; +} + +static GstClapperVideoInfo * +gst_clapper_video_info_new (void) +{ + return g_object_new (GST_TYPE_CLAPPER_VIDEO_INFO, NULL); +} + +static GstClapperAudioInfo * +gst_clapper_audio_info_new (void) +{ + return g_object_new (GST_TYPE_CLAPPER_AUDIO_INFO, NULL); +} + +static GstClapperSubtitleInfo * +gst_clapper_subtitle_info_new (void) +{ + return g_object_new (GST_TYPE_CLAPPER_SUBTITLE_INFO, NULL); +} + +static GstClapperStreamInfo * +gst_clapper_video_info_copy (GstClapperVideoInfo * ref) +{ + GstClapperVideoInfo *ret; + + ret = gst_clapper_video_info_new (); + + ret->width = ref->width; + ret->height = ref->height; + ret->framerate_num = ref->framerate_num; + ret->framerate_denom = ref->framerate_denom; + ret->par_num = ref->par_num; + ret->par_denom = ref->par_denom; + ret->bitrate = ref->bitrate; + ret->max_bitrate = ref->max_bitrate; + + return (GstClapperStreamInfo *) ret; +} + +static GstClapperStreamInfo * +gst_clapper_audio_info_copy (GstClapperAudioInfo * ref) +{ + GstClapperAudioInfo *ret; + + ret = gst_clapper_audio_info_new (); + + ret->sample_rate = ref->sample_rate; + ret->channels = ref->channels; + ret->bitrate = ref->bitrate; + ret->max_bitrate = ref->max_bitrate; + + if (ref->language) + ret->language = g_strdup (ref->language); + + return (GstClapperStreamInfo *) ret; +} + +static GstClapperStreamInfo * +gst_clapper_subtitle_info_copy (GstClapperSubtitleInfo * ref) +{ + GstClapperSubtitleInfo *ret; + + ret = gst_clapper_subtitle_info_new (); + if (ref->language) + ret->language = g_strdup (ref->language); + + return (GstClapperStreamInfo *) ret; +} + +GstClapperStreamInfo * +gst_clapper_stream_info_copy (GstClapperStreamInfo * ref) +{ + GstClapperStreamInfo *info = NULL; + + if (!ref) + return NULL; + + if (GST_IS_CLAPPER_VIDEO_INFO (ref)) + info = gst_clapper_video_info_copy ((GstClapperVideoInfo *) ref); + else if (GST_IS_CLAPPER_AUDIO_INFO (ref)) + info = gst_clapper_audio_info_copy ((GstClapperAudioInfo *) ref); + else + info = gst_clapper_subtitle_info_copy ((GstClapperSubtitleInfo *) ref); + + info->stream_index = ref->stream_index; + if (ref->tags) + info->tags = gst_tag_list_ref (ref->tags); + if (ref->caps) + info->caps = gst_caps_copy (ref->caps); + if (ref->codec) + info->codec = g_strdup (ref->codec); + if (ref->stream_id) + info->stream_id = g_strdup (ref->stream_id); + + return info; +} + +GstClapperMediaInfo * +gst_clapper_media_info_copy (GstClapperMediaInfo * ref) +{ + GList *l; + GstClapperMediaInfo *info; + + if (!ref) + return NULL; + + info = gst_clapper_media_info_new (ref->uri); + info->duration = ref->duration; + info->seekable = ref->seekable; + info->is_live = ref->is_live; + if (ref->tags) + info->tags = gst_tag_list_ref (ref->tags); + if (ref->title) + info->title = g_strdup (ref->title); + if (ref->container) + info->container = g_strdup (ref->container); + if (ref->image_sample) + info->image_sample = gst_sample_ref (ref->image_sample); + + for (l = ref->stream_list; l != NULL; l = l->next) { + GstClapperStreamInfo *s; + + s = gst_clapper_stream_info_copy ((GstClapperStreamInfo *) l->data); + info->stream_list = g_list_append (info->stream_list, s); + + if (GST_IS_CLAPPER_AUDIO_INFO (s)) + info->audio_stream_list = g_list_append (info->audio_stream_list, s); + else if (GST_IS_CLAPPER_VIDEO_INFO (s)) + info->video_stream_list = g_list_append (info->video_stream_list, s); + else + info->subtitle_stream_list = + g_list_append (info->subtitle_stream_list, s); + } + + return info; +} + +GstClapperStreamInfo * +gst_clapper_stream_info_new (gint stream_index, GType type) +{ + GstClapperStreamInfo *info = NULL; + + if (type == GST_TYPE_CLAPPER_AUDIO_INFO) + info = (GstClapperStreamInfo *) gst_clapper_audio_info_new (); + else if (type == GST_TYPE_CLAPPER_VIDEO_INFO) + info = (GstClapperStreamInfo *) gst_clapper_video_info_new (); + else + info = (GstClapperStreamInfo *) gst_clapper_subtitle_info_new (); + + info->stream_index = stream_index; + + return info; +} + +GstClapperMediaInfo * +gst_clapper_media_info_new (const gchar * uri) +{ + GstClapperMediaInfo *info; + + g_return_val_if_fail (uri != NULL, NULL); + + info = g_object_new (GST_TYPE_CLAPPER_MEDIA_INFO, NULL); + info->uri = g_strdup (uri); + + return info; +} + +/** + * gst_clapper_media_info_get_uri: + * @info: a #GstClapperMediaInfo + * + * Returns: the URI associated with #GstClapperMediaInfo. + */ +const gchar * +gst_clapper_media_info_get_uri (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->uri; +} + +/** + * gst_clapper_media_info_is_seekable: + * @info: a #GstClapperMediaInfo + * + * Returns: %TRUE if the media is seekable. + */ +gboolean +gst_clapper_media_info_is_seekable (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), FALSE); + + return info->seekable; +} + +/** + * gst_clapper_media_info_is_live: + * @info: a #GstClapperMediaInfo + * + * Returns: %TRUE if the media is live. + */ +gboolean +gst_clapper_media_info_is_live (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), FALSE); + + return info->is_live; +} + +/** + * gst_clapper_media_info_get_stream_list: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none) (element-type GstClapperStreamInfo): A #GList of + * matching #GstClapperStreamInfo. + */ +GList * +gst_clapper_media_info_get_stream_list (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->stream_list; +} + +/** + * gst_clapper_media_info_get_video_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none) (element-type GstClapperVideoInfo): A #GList of + * matching #GstClapperVideoInfo. + */ +GList * +gst_clapper_media_info_get_video_streams (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->video_stream_list; +} + +/** + * gst_clapper_media_info_get_subtitle_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none) (element-type GstClapperSubtitleInfo): A #GList of + * matching #GstClapperSubtitleInfo. + */ +GList * +gst_clapper_media_info_get_subtitle_streams (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->subtitle_stream_list; +} + +/** + * gst_clapper_media_info_get_audio_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none) (element-type GstClapperAudioInfo): A #GList of + * matching #GstClapperAudioInfo. + */ +GList * +gst_clapper_media_info_get_audio_streams (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->audio_stream_list; +} + +/** + * gst_clapper_media_info_get_duration: + * @info: a #GstClapperMediaInfo + * + * Returns: duration of the media. + */ +GstClockTime +gst_clapper_media_info_get_duration (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), -1); + + return info->duration; +} + +/** + * gst_clapper_media_info_get_tags: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none): the tags contained in media info. + */ +GstTagList * +gst_clapper_media_info_get_tags (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->tags; +} + +/** + * gst_clapper_media_info_get_title: + * @info: a #GstClapperMediaInfo + * + * Returns: the media title. + */ +const gchar * +gst_clapper_media_info_get_title (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->title; +} + +/** + * gst_clapper_media_info_get_container_format: + * @info: a #GstClapperMediaInfo + * + * Returns: the container format. + */ +const gchar * +gst_clapper_media_info_get_container_format (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->container; +} + +/** + * gst_clapper_media_info_get_image_sample: + * @info: a #GstClapperMediaInfo + * + * Function to get the image (or preview-image) stored in taglist. + * Application can use `gst_sample_*_()` API's to get caps, buffer etc. + * + * Returns: (transfer none): GstSample or NULL. + */ +GstSample * +gst_clapper_media_info_get_image_sample (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->image_sample; +} + +/** + * gst_clapper_media_info_get_number_of_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: number of total streams. + */ +guint +gst_clapper_media_info_get_number_of_streams (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), 0); + + return g_list_length (info->stream_list); +} + +/** + * gst_clapper_media_info_get_number_of_video_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: number of video streams. + */ +guint +gst_clapper_media_info_get_number_of_video_streams (const GstClapperMediaInfo * + info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), 0); + + return g_list_length (info->video_stream_list); +} + +/** + * gst_clapper_media_info_get_number_of_audio_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: number of audio streams. + */ +guint +gst_clapper_media_info_get_number_of_audio_streams (const GstClapperMediaInfo * + info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), 0); + + return g_list_length (info->audio_stream_list); +} + +/** + * gst_clapper_media_info_get_number_of_subtitle_streams: + * @info: a #GstClapperMediaInfo + * + * Returns: number of subtitle streams. + */ +guint gst_clapper_media_info_get_number_of_subtitle_streams + (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), 0); + + return g_list_length (info->subtitle_stream_list); +} diff --git a/lib/gst/clapper/gstclapper-media-info.h b/lib/gst/clapper/gstclapper-media-info.h new file mode 100644 index 00000000..e926366b --- /dev/null +++ b/lib/gst/clapper/gstclapper-media-info.h @@ -0,0 +1,247 @@ +/* GStreamer + * + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_MEDIA_INFO_H__ +#define __GST_CLAPPER_MEDIA_INFO_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_STREAM_INFO \ + (gst_clapper_stream_info_get_type ()) +#define GST_CLAPPER_STREAM_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_STREAM_INFO,GstClapperStreamInfo)) +#define GST_CLAPPER_STREAM_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CLAPPER_STREAM_INFO,GstClapperStreamInfo)) +#define GST_IS_CLAPPER_STREAM_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_STREAM_INFO)) +#define GST_IS_CLAPPER_STREAM_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CLAPPER_STREAM_INFO)) + +/** + * GstClapperStreamInfo: + * + * Base structure for information concerning a media stream. Depending on + * the stream type, one can find more media-specific information in + * #GstClapperVideoInfo, #GstClapperAudioInfo, #GstClapperSubtitleInfo. + */ +typedef struct _GstClapperStreamInfo GstClapperStreamInfo; +typedef struct _GstClapperStreamInfoClass GstClapperStreamInfoClass; + +GST_CLAPPER_API +GType gst_clapper_stream_info_get_type (void); + +GST_CLAPPER_API +gint gst_clapper_stream_info_get_index (const GstClapperStreamInfo *info); + +GST_CLAPPER_API +const gchar* gst_clapper_stream_info_get_stream_type (const GstClapperStreamInfo *info); + +GST_CLAPPER_API +GstTagList* gst_clapper_stream_info_get_tags (const GstClapperStreamInfo *info); + +GST_CLAPPER_API +GstCaps* gst_clapper_stream_info_get_caps (const GstClapperStreamInfo *info); + +GST_CLAPPER_API +const gchar* gst_clapper_stream_info_get_codec (const GstClapperStreamInfo *info); + +#define GST_TYPE_CLAPPER_VIDEO_INFO \ + (gst_clapper_video_info_get_type ()) +#define GST_CLAPPER_VIDEO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_VIDEO_INFO, GstClapperVideoInfo)) +#define GST_CLAPPER_VIDEO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((obj),GST_TYPE_CLAPPER_VIDEO_INFO, GstClapperVideoInfoClass)) +#define GST_IS_CLAPPER_VIDEO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_VIDEO_INFO)) +#define GST_IS_CLAPPER_VIDEO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((obj),GST_TYPE_CLAPPER_VIDEO_INFO)) + +/** + * GstClapperVideoInfo: + * + * #GstClapperStreamInfo specific to video streams. + */ +typedef struct _GstClapperVideoInfo GstClapperVideoInfo; +typedef struct _GstClapperVideoInfoClass GstClapperVideoInfoClass; + +GST_CLAPPER_API +GType gst_clapper_video_info_get_type (void); + +GST_CLAPPER_API +gint gst_clapper_video_info_get_bitrate (const GstClapperVideoInfo * info); + +GST_CLAPPER_API +gint gst_clapper_video_info_get_max_bitrate (const GstClapperVideoInfo * info); + +GST_CLAPPER_API +gint gst_clapper_video_info_get_width (const GstClapperVideoInfo * info); + +GST_CLAPPER_API +gint gst_clapper_video_info_get_height (const GstClapperVideoInfo * info); + +GST_CLAPPER_API +void gst_clapper_video_info_get_framerate (const GstClapperVideoInfo * info, + gint * fps_n, + gint * fps_d); + +GST_CLAPPER_API +void gst_clapper_video_info_get_pixel_aspect_ratio (const GstClapperVideoInfo * info, + guint * par_n, + guint * par_d); + +#define GST_TYPE_CLAPPER_AUDIO_INFO \ + (gst_clapper_audio_info_get_type ()) +#define GST_CLAPPER_AUDIO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_AUDIO_INFO, GstClapperAudioInfo)) +#define GST_CLAPPER_AUDIO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CLAPPER_AUDIO_INFO, GstClapperAudioInfoClass)) +#define GST_IS_CLAPPER_AUDIO_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_AUDIO_INFO)) +#define GST_IS_CLAPPER_AUDIO_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CLAPPER_AUDIO_INFO)) + +/** + * GstClapperAudioInfo: + * + * #GstClapperStreamInfo specific to audio streams. + */ +typedef struct _GstClapperAudioInfo GstClapperAudioInfo; +typedef struct _GstClapperAudioInfoClass GstClapperAudioInfoClass; + +GST_CLAPPER_API +GType gst_clapper_audio_info_get_type (void); + +GST_CLAPPER_API +gint gst_clapper_audio_info_get_channels (const GstClapperAudioInfo* info); + +GST_CLAPPER_API +gint gst_clapper_audio_info_get_sample_rate (const GstClapperAudioInfo* info); + +GST_CLAPPER_API +gint gst_clapper_audio_info_get_bitrate (const GstClapperAudioInfo* info); + +GST_CLAPPER_API +gint gst_clapper_audio_info_get_max_bitrate (const GstClapperAudioInfo* info); + +GST_CLAPPER_API +const gchar* gst_clapper_audio_info_get_language (const GstClapperAudioInfo* info); + +#define GST_TYPE_CLAPPER_SUBTITLE_INFO \ + (gst_clapper_subtitle_info_get_type ()) +#define GST_CLAPPER_SUBTITLE_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_SUBTITLE_INFO, GstClapperSubtitleInfo)) +#define GST_CLAPPER_SUBTITLE_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CLAPPER_SUBTITLE_INFO,GstClapperSubtitleInfoClass)) +#define GST_IS_CLAPPER_SUBTITLE_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_SUBTITLE_INFO)) +#define GST_IS_CLAPPER_SUBTITLE_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CLAPPER_SUBTITLE_INFO)) + +/** + * GstClapperSubtitleInfo: + * + * #GstClapperStreamInfo specific to subtitle streams. + */ +typedef struct _GstClapperSubtitleInfo GstClapperSubtitleInfo; +typedef struct _GstClapperSubtitleInfoClass GstClapperSubtitleInfoClass; + +GST_CLAPPER_API +GType gst_clapper_subtitle_info_get_type (void); + +GST_CLAPPER_API +const gchar * gst_clapper_subtitle_info_get_language (const GstClapperSubtitleInfo* info); + +#define GST_TYPE_CLAPPER_MEDIA_INFO \ + (gst_clapper_media_info_get_type()) +#define GST_CLAPPER_MEDIA_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_MEDIA_INFO,GstClapperMediaInfo)) +#define GST_CLAPPER_MEDIA_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CLAPPER_MEDIA_INFO,GstClapperMediaInfoClass)) +#define GST_IS_CLAPPER_MEDIA_INFO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_MEDIA_INFO)) +#define GST_IS_CLAPPER_MEDIA_INFO_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CLAPPER_MEDIA_INFO)) + +/** + * GstClapperMediaInfo: + * + * Structure containing the media information of a URI. + */ +typedef struct _GstClapperMediaInfo GstClapperMediaInfo; +typedef struct _GstClapperMediaInfoClass GstClapperMediaInfoClass; + +GST_CLAPPER_API +GType gst_clapper_media_info_get_type (void); + +GST_CLAPPER_API +const gchar * gst_clapper_media_info_get_uri (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +gboolean gst_clapper_media_info_is_seekable (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +gboolean gst_clapper_media_info_is_live (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GstClockTime gst_clapper_media_info_get_duration (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GList * gst_clapper_media_info_get_stream_list (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +guint gst_clapper_media_info_get_number_of_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GList * gst_clapper_media_info_get_video_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +guint gst_clapper_media_info_get_number_of_video_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GList * gst_clapper_media_info_get_audio_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +guint gst_clapper_media_info_get_number_of_audio_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GList * gst_clapper_media_info_get_subtitle_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +guint gst_clapper_media_info_get_number_of_subtitle_streams (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GstTagList * gst_clapper_media_info_get_tags (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +const gchar * gst_clapper_media_info_get_title (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +const gchar * gst_clapper_media_info_get_container_format (const GstClapperMediaInfo *info); + +GST_CLAPPER_API +GstSample * gst_clapper_media_info_get_image_sample (const GstClapperMediaInfo *info); + +G_END_DECLS + +#endif /* __GST_CLAPPER_MEDIA_INFO_H */ diff --git a/lib/gst/clapper/gstclapper-signal-dispatcher-private.h b/lib/gst/clapper/gstclapper-signal-dispatcher-private.h new file mode 100644 index 00000000..91dc88e4 --- /dev/null +++ b/lib/gst/clapper/gstclapper-signal-dispatcher-private.h @@ -0,0 +1,35 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_SIGNAL_DISPATCHER_PRIVATE_H__ +#define __GST_CLAPPER_SIGNAL_DISPATCHER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL void gst_clapper_signal_dispatcher_dispatch (GstClapperSignalDispatcher * self, + GstClapper * clapper, GstClapperSignalDispatcherFunc emitter, gpointer data, + GDestroyNotify destroy); + +G_END_DECLS + +#endif /* __GST_CLAPPER_SIGNAL_DISPATCHER_PRIVATE_H__ */ diff --git a/lib/gst/clapper/gstclapper-signal-dispatcher.c b/lib/gst/clapper/gstclapper-signal-dispatcher.c new file mode 100644 index 00000000..47bbdd04 --- /dev/null +++ b/lib/gst/clapper/gstclapper-signal-dispatcher.c @@ -0,0 +1,58 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-signal-dispatcher.h" +#include "gstclapper-signal-dispatcher-private.h" + +G_DEFINE_INTERFACE (GstClapperSignalDispatcher, gst_clapper_signal_dispatcher, + G_TYPE_OBJECT); + +static void +gst_clapper_signal_dispatcher_default_init (G_GNUC_UNUSED + GstClapperSignalDispatcherInterface * iface) +{ + +} + +void +gst_clapper_signal_dispatcher_dispatch (GstClapperSignalDispatcher * self, + GstClapper * clapper, GstClapperSignalDispatcherFunc emitter, gpointer data, + GDestroyNotify destroy) +{ + GstClapperSignalDispatcherInterface *iface; + + if (!self) { + emitter (data); + if (destroy) + destroy (data); + return; + } + + g_return_if_fail (GST_IS_CLAPPER_SIGNAL_DISPATCHER (self)); + iface = GST_CLAPPER_SIGNAL_DISPATCHER_GET_INTERFACE (self); + g_return_if_fail (iface->dispatch != NULL); + + iface->dispatch (self, clapper, emitter, data, destroy); +} diff --git a/lib/gst/clapper/gstclapper-signal-dispatcher.h b/lib/gst/clapper/gstclapper-signal-dispatcher.h new file mode 100644 index 00000000..ebab4a6c --- /dev/null +++ b/lib/gst/clapper/gstclapper-signal-dispatcher.h @@ -0,0 +1,53 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_SIGNAL_DISPATCHER_H__ +#define __GST_CLAPPER_SIGNAL_DISPATCHER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperSignalDispatcher GstClapperSignalDispatcher; +typedef struct _GstClapperSignalDispatcherInterface GstClapperSignalDispatcherInterface; + +#define GST_TYPE_CLAPPER_SIGNAL_DISPATCHER (gst_clapper_signal_dispatcher_get_type ()) +#define GST_CLAPPER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_SIGNAL_DISPATCHER, GstClapperSignalDispatcher)) +#define GST_IS_CLAPPER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_SIGNAL_DISPATCHER)) +#define GST_CLAPPER_SIGNAL_DISPATCHER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_CLAPPER_SIGNAL_DISPATCHER, GstClapperSignalDispatcherInterface)) + +typedef void (*GstClapperSignalDispatcherFunc) (gpointer data); + +struct _GstClapperSignalDispatcherInterface { + GTypeInterface parent_iface; + + void (*dispatch) (GstClapperSignalDispatcher * self, GstClapper * clapper, + GstClapperSignalDispatcherFunc emitter, gpointer data, + GDestroyNotify destroy); +}; + +GST_CLAPPER_API +GType gst_clapper_signal_dispatcher_get_type (void); + +G_END_DECLS + +#endif /* __GST_CLAPPER_SIGNAL_DISPATCHER_H__ */ diff --git a/lib/gst/clapper/gstclapper-types.h b/lib/gst/clapper/gstclapper-types.h new file mode 100644 index 00000000..a84429a7 --- /dev/null +++ b/lib/gst/clapper/gstclapper-types.h @@ -0,0 +1,37 @@ +/* GStreamer + * + * Copyright (C) 2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_TYPES_H__ +#define __GST_CLAPPER_TYPES_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapper GstClapper; +typedef struct _GstClapperClass GstClapperClass; + +G_END_DECLS + +#endif /* __GST_CLAPPER_TYPES_H__ */ + + diff --git a/lib/gst/clapper/gstclapper-video-overlay-video-renderer.c b/lib/gst/clapper/gstclapper-video-overlay-video-renderer.c new file mode 100644 index 00000000..476184d1 --- /dev/null +++ b/lib/gst/clapper/gstclapper-video-overlay-video-renderer.c @@ -0,0 +1,345 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-videooverlayvideorenderer + * @title: GstClapperVideoOverlayVideoRenderer + * @short_description: Clapper Video Overlay Video Renderer + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-video-overlay-video-renderer.h" +#include "gstclapper.h" + +#include + +struct _GstClapperVideoOverlayVideoRenderer +{ + GObject parent; + + GstVideoOverlay *video_overlay; + gpointer window_handle; + gint x, y, width, height; + + GstElement *video_sink; /* configured video sink, or NULL */ +}; + +struct _GstClapperVideoOverlayVideoRendererClass +{ + GObjectClass parent_class; +}; + +static void + gst_clapper_video_overlay_video_renderer_interface_init + (GstClapperVideoRendererInterface * iface); + +enum +{ + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_0, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstClapperVideoOverlayVideoRenderer, + gst_clapper_video_overlay_video_renderer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_CLAPPER_VIDEO_RENDERER, + gst_clapper_video_overlay_video_renderer_interface_init)); + +static GParamSpec + * video_overlay_video_renderer_param_specs + [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST] = { NULL, }; + +static void +gst_clapper_video_overlay_video_renderer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstClapperVideoOverlayVideoRenderer *self = + GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (object); + + switch (prop_id) { + case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE: + self->window_handle = g_value_get_pointer (value); + if (self->video_overlay) + gst_video_overlay_set_window_handle (self->video_overlay, + (guintptr) self->window_handle); + break; + case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK: + self->video_sink = gst_object_ref_sink (g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_video_overlay_video_renderer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstClapperVideoOverlayVideoRenderer *self = + GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (object); + + switch (prop_id) { + case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE: + g_value_set_pointer (value, self->window_handle); + break; + case VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK: + g_value_set_object (value, self->video_sink); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_video_overlay_video_renderer_finalize (GObject * object) +{ + GstClapperVideoOverlayVideoRenderer *self = + GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (object); + + if (self->video_overlay) + gst_object_unref (self->video_overlay); + + if (self->video_sink) + gst_object_unref (self->video_sink); + + G_OBJECT_CLASS + (gst_clapper_video_overlay_video_renderer_parent_class)->finalize (object); +} + +static void + gst_clapper_video_overlay_video_renderer_class_init + (GstClapperVideoOverlayVideoRendererClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = + gst_clapper_video_overlay_video_renderer_set_property; + gobject_class->get_property = + gst_clapper_video_overlay_video_renderer_get_property; + gobject_class->finalize = gst_clapper_video_overlay_video_renderer_finalize; + + video_overlay_video_renderer_param_specs + [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_WINDOW_HANDLE] = + g_param_spec_pointer ("window-handle", "Window Handle", + "Window handle to embed the video into", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + video_overlay_video_renderer_param_specs + [VIDEO_OVERLAY_VIDEO_RENDERER_PROP_VIDEO_SINK] = + g_param_spec_object ("video-sink", "Video Sink", + "the video output element to use (NULL = default sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + VIDEO_OVERLAY_VIDEO_RENDERER_PROP_LAST, + video_overlay_video_renderer_param_specs); +} + +static void + gst_clapper_video_overlay_video_renderer_init + (GstClapperVideoOverlayVideoRenderer * self) +{ + self->x = self->y = self->width = self->height = -1; + self->video_sink = NULL; +} + +static GstElement *gst_clapper_video_overlay_video_renderer_create_video_sink + (GstClapperVideoRenderer * iface, GstClapper * clapper) +{ + GstElement *video_overlay; + GstClapperVideoOverlayVideoRenderer *self = + GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (iface); + + if (self->video_overlay) + gst_object_unref (self->video_overlay); + + video_overlay = gst_clapper_get_pipeline (clapper); + g_return_val_if_fail (GST_IS_VIDEO_OVERLAY (video_overlay), NULL); + + self->video_overlay = GST_VIDEO_OVERLAY (video_overlay); + + gst_video_overlay_set_window_handle (self->video_overlay, + (guintptr) self->window_handle); + if (self->width != -1 || self->height != -1) + gst_video_overlay_set_render_rectangle (self->video_overlay, self->x, + self->y, self->width, self->height); + + return self->video_sink; +} + +static void + gst_clapper_video_overlay_video_renderer_interface_init + (GstClapperVideoRendererInterface * iface) +{ + iface->create_video_sink = + gst_clapper_video_overlay_video_renderer_create_video_sink; +} + +/** + * gst_clapper_video_overlay_video_renderer_new: + * @window_handle: (allow-none): Window handle to use or %NULL + * + * Returns: (transfer full): + */ +GstClapperVideoRenderer * +gst_clapper_video_overlay_video_renderer_new (gpointer window_handle) +{ + return g_object_new (GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER, + "window-handle", window_handle, NULL); +} + +/** + * gst_clapper_video_overlay_video_renderer_new_with_sink: + * @window_handle: (allow-none): Window handle to use or %NULL + * @video_sink: (transfer floating): the custom video_sink element to be set for the video renderer + * + * Returns: (transfer full): clapper video renderer + */ +GstClapperVideoRenderer * +gst_clapper_video_overlay_video_renderer_new_with_sink (gpointer window_handle, + GstElement * video_sink) +{ + return g_object_new (GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER, + "window-handle", window_handle, "video-sink", video_sink, NULL); +} + +/** + * gst_clapper_video_overlay_video_renderer_set_window_handle: + * @self: #GstClapperVideoRenderer instance + * @window_handle: handle referencing to the platform specific window + * + * Sets the platform specific window handle into which the video + * should be rendered + **/ +void gst_clapper_video_overlay_video_renderer_set_window_handle + (GstClapperVideoOverlayVideoRenderer * self, gpointer window_handle) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (self)); + + g_object_set (self, "window-handle", window_handle, NULL); +} + +/** + * gst_clapper_video_overlay_video_renderer_get_window_handle: + * @self: #GstClapperVideoRenderer instance + * + * Returns: (transfer none): The currently set, platform specific window + * handle + */ +gpointer + gst_clapper_video_overlay_video_renderer_get_window_handle + (GstClapperVideoOverlayVideoRenderer * self) { + gpointer window_handle; + + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (self), + NULL); + + g_object_get (self, "window-handle", &window_handle, NULL); + + return window_handle; +} + +/** + * gst_clapper_video_overlay_video_renderer_expose: + * @self: a #GstClapperVideoOverlayVideoRenderer instance. + * + * Tell an overlay that it has been exposed. This will redraw the current frame + * in the drawable even if the pipeline is PAUSED. + */ +void gst_clapper_video_overlay_video_renderer_expose + (GstClapperVideoOverlayVideoRenderer * self) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (self)); + + if (self->video_overlay) + gst_video_overlay_expose (self->video_overlay); +} + +/** + * gst_clapper_video_overlay_video_renderer_set_render_rectangle: + * @self: a #GstClapperVideoOverlayVideoRenderer instance + * @x: the horizontal offset of the render area inside the window + * @y: the vertical offset of the render area inside the window + * @width: the width of the render area inside the window + * @height: the height of the render area inside the window + * + * Configure a subregion as a video target within the window set by + * gst_clapper_video_overlay_video_renderer_set_window_handle(). If this is not + * used or not supported the video will fill the area of the window set as the + * overlay to 100%. By specifying the rectangle, the video can be overlaid to + * a specific region of that window only. After setting the new rectangle one + * should call gst_clapper_video_overlay_video_renderer_expose() to force a + * redraw. To unset the region pass -1 for the @width and @height parameters. + * + * This method is needed for non fullscreen video overlay in UI toolkits that + * do not support subwindows. + * + */ +void gst_clapper_video_overlay_video_renderer_set_render_rectangle + (GstClapperVideoOverlayVideoRenderer * self, gint x, gint y, gint width, + gint height) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (self)); + + self->x = x; + self->y = y; + self->width = width; + self->height = height; + + if (self->video_overlay) + gst_video_overlay_set_render_rectangle (self->video_overlay, + x, y, width, height); +} + +/** + * gst_clapper_video_overlay_video_renderer_get_render_rectangle: + * @self: a #GstClapperVideoOverlayVideoRenderer instance + * @x: (out) (allow-none): the horizontal offset of the render area inside the window + * @y: (out) (allow-none): the vertical offset of the render area inside the window + * @width: (out) (allow-none): the width of the render area inside the window + * @height: (out) (allow-none): the height of the render area inside the window + * + * Return the currently configured render rectangle. See gst_clapper_video_overlay_video_renderer_set_render_rectangle() + * for details. + * + */ +void gst_clapper_video_overlay_video_renderer_get_render_rectangle + (GstClapperVideoOverlayVideoRenderer * self, gint * x, gint * y, + gint * width, gint * height) +{ + g_return_if_fail (GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (self)); + + if (x) + *x = self->x; + if (y) + *y = self->y; + if (width) + *width = self->width; + if (height) + *height = self->height; +} diff --git a/lib/gst/clapper/gstclapper-video-overlay-video-renderer.h b/lib/gst/clapper/gstclapper-video-overlay-video-renderer.h new file mode 100644 index 00000000..4c250716 --- /dev/null +++ b/lib/gst/clapper/gstclapper-video-overlay-video-renderer.h @@ -0,0 +1,71 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ +#define __GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperVideoOverlayVideoRenderer + GstClapperVideoOverlayVideoRenderer; +typedef struct _GstClapperVideoOverlayVideoRendererClass + GstClapperVideoOverlayVideoRendererClass; + +#define GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER (gst_clapper_video_overlay_video_renderer_get_type ()) +#define GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER)) +#define GST_IS_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER)) +#define GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER, GstClapperVideoOverlayVideoRendererClass)) +#define GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER, GstClapperVideoOverlayVideoRenderer)) +#define GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER, GstClapperVideoOverlayVideoRendererClass)) +#define GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_CAST(obj) ((GstClapperVideoOverlayVideoRenderer*)(obj)) + +GST_CLAPPER_API +GType gst_clapper_video_overlay_video_renderer_get_type (void); + +GST_CLAPPER_API +GstClapperVideoRenderer * + gst_clapper_video_overlay_video_renderer_new (gpointer window_handle); + +GST_CLAPPER_API +GstClapperVideoRenderer * + gst_clapper_video_overlay_video_renderer_new_with_sink (gpointer window_handle, GstElement *video_sink); + +GST_CLAPPER_API +void gst_clapper_video_overlay_video_renderer_set_window_handle (GstClapperVideoOverlayVideoRenderer *self, gpointer window_handle); + +GST_CLAPPER_API +gpointer gst_clapper_video_overlay_video_renderer_get_window_handle (GstClapperVideoOverlayVideoRenderer *self); + +GST_CLAPPER_API +void gst_clapper_video_overlay_video_renderer_expose (GstClapperVideoOverlayVideoRenderer *self); + +GST_CLAPPER_API +void gst_clapper_video_overlay_video_renderer_set_render_rectangle (GstClapperVideoOverlayVideoRenderer *self, gint x, gint y, gint width, gint height); + +GST_CLAPPER_API +void gst_clapper_video_overlay_video_renderer_get_render_rectangle (GstClapperVideoOverlayVideoRenderer *self, gint *x, gint *y, gint *width, gint *height); + +G_END_DECLS + +#endif /* __GST_CLAPPER_VIDEO_OVERLAY_VIDEO_RENDERER_H__ */ diff --git a/lib/gst/clapper/gstclapper-video-renderer-private.h b/lib/gst/clapper/gstclapper-video-renderer-private.h new file mode 100644 index 00000000..96a07401 --- /dev/null +++ b/lib/gst/clapper/gstclapper-video-renderer-private.h @@ -0,0 +1,33 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_VIDEO_RENDERER_PRIVATE_H__ +#define __GST_CLAPPER_VIDEO_RENDERER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL GstElement * gst_clapper_video_renderer_create_video_sink (GstClapperVideoRenderer *self, GstClapper *clapper); + +G_END_DECLS + +#endif /* __GST_CLAPPER_VIDEO_RENDERER_PRIVATE_H__ */ diff --git a/lib/gst/clapper/gstclapper-video-renderer.c b/lib/gst/clapper/gstclapper-video-renderer.c new file mode 100644 index 00000000..ff44be73 --- /dev/null +++ b/lib/gst/clapper/gstclapper-video-renderer.c @@ -0,0 +1,50 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-video-renderer.h" +#include "gstclapper-video-renderer-private.h" + +G_DEFINE_INTERFACE (GstClapperVideoRenderer, gst_clapper_video_renderer, + G_TYPE_OBJECT); + +static void +gst_clapper_video_renderer_default_init (G_GNUC_UNUSED + GstClapperVideoRendererInterface * iface) +{ + +} + +GstElement * +gst_clapper_video_renderer_create_video_sink (GstClapperVideoRenderer * self, + GstClapper * clapper) +{ + GstClapperVideoRendererInterface *iface; + + g_return_val_if_fail (GST_IS_CLAPPER_VIDEO_RENDERER (self), NULL); + iface = GST_CLAPPER_VIDEO_RENDERER_GET_INTERFACE (self); + g_return_val_if_fail (iface->create_video_sink != NULL, NULL); + + return iface->create_video_sink (self, clapper); +} diff --git a/lib/gst/clapper/gstclapper-video-renderer.h b/lib/gst/clapper/gstclapper-video-renderer.h new file mode 100644 index 00000000..9ef43fa6 --- /dev/null +++ b/lib/gst/clapper/gstclapper-video-renderer.h @@ -0,0 +1,49 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_VIDEO_RENDERER_H__ +#define __GST_CLAPPER_VIDEO_RENDERER_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperVideoRenderer GstClapperVideoRenderer; +typedef struct _GstClapperVideoRendererInterface GstClapperVideoRendererInterface; + +#define GST_TYPE_CLAPPER_VIDEO_RENDERER (gst_clapper_video_renderer_get_type ()) +#define GST_CLAPPER_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_VIDEO_RENDERER, GstClapperVideoRenderer)) +#define GST_IS_CLAPPER_VIDEO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_VIDEO_RENDERER)) +#define GST_CLAPPER_VIDEO_RENDERER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_CLAPPER_VIDEO_RENDERER, GstClapperVideoRendererInterface)) + +struct _GstClapperVideoRendererInterface { + GTypeInterface parent_iface; + + GstElement * (*create_video_sink) (GstClapperVideoRenderer * self, GstClapper * clapper); +}; + +GST_CLAPPER_API +GType gst_clapper_video_renderer_get_type (void); + +G_END_DECLS + +#endif /* __GST_CLAPPER_VIDEO_RENDERER_H__ */ diff --git a/lib/gst/clapper/gstclapper-visualization.c b/lib/gst/clapper/gstclapper-visualization.c new file mode 100644 index 00000000..05e3c3c2 --- /dev/null +++ b/lib/gst/clapper/gstclapper-visualization.c @@ -0,0 +1,180 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-visualization + * @title: GstClapperVisualization + * @short_description: Clapper Visualization + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-visualization.h" + +#include + +static GMutex vis_lock; +static GQueue vis_list = G_QUEUE_INIT; +static guint32 vis_cookie; + +G_DEFINE_BOXED_TYPE (GstClapperVisualization, gst_clapper_visualization, + (GBoxedCopyFunc) gst_clapper_visualization_copy, + (GBoxedFreeFunc) gst_clapper_visualization_free); + +/** + * gst_clapper_visualization_free: + * @vis: #GstClapperVisualization instance + * + * Frees a #GstClapperVisualization. + */ +void +gst_clapper_visualization_free (GstClapperVisualization * vis) +{ + g_return_if_fail (vis != NULL); + + g_free (vis->name); + g_free (vis->description); + g_free (vis); +} + +/** + * gst_clapper_visualization_copy: + * @vis: #GstClapperVisualization instance + * + * Makes a copy of the #GstClapperVisualization. The result must be + * freed using gst_clapper_visualization_free(). + * + * Returns: (transfer full): an allocated copy of @vis. + */ +GstClapperVisualization * +gst_clapper_visualization_copy (const GstClapperVisualization * vis) +{ + GstClapperVisualization *ret; + + g_return_val_if_fail (vis != NULL, NULL); + + ret = g_new0 (GstClapperVisualization, 1); + ret->name = vis->name ? g_strdup (vis->name) : NULL; + ret->description = vis->description ? g_strdup (vis->description) : NULL; + + return ret; +} + +/** + * gst_clapper_visualizations_free: + * @viss: a %NULL terminated array of #GstClapperVisualization to free + * + * Frees a %NULL terminated array of #GstClapperVisualization. + */ +void +gst_clapper_visualizations_free (GstClapperVisualization ** viss) +{ + GstClapperVisualization **p; + + g_return_if_fail (viss != NULL); + + p = viss; + while (*p) { + g_free ((*p)->name); + g_free ((*p)->description); + g_free (*p); + p++; + } + g_free (viss); +} + +static void +gst_clapper_update_visualization_list (void) +{ + GList *features; + GList *l; + guint32 cookie; + GstClapperVisualization *vis; + + g_mutex_lock (&vis_lock); + + /* check if we need to update the list */ + cookie = gst_registry_get_feature_list_cookie (gst_registry_get ()); + if (vis_cookie == cookie) { + g_mutex_unlock (&vis_lock); + return; + } + + /* if update is needed then first free the existing list */ + while ((vis = g_queue_pop_head (&vis_list))) + gst_clapper_visualization_free (vis); + + features = gst_registry_get_feature_list (gst_registry_get (), + GST_TYPE_ELEMENT_FACTORY); + + for (l = features; l; l = l->next) { + GstPluginFeature *feature = l->data; + const gchar *klass; + + klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY (feature), + GST_ELEMENT_METADATA_KLASS); + + if (strstr (klass, "Visualization")) { + vis = g_new0 (GstClapperVisualization, 1); + + vis->name = g_strdup (gst_plugin_feature_get_name (feature)); + vis->description = + g_strdup (gst_element_factory_get_metadata (GST_ELEMENT_FACTORY + (feature), GST_ELEMENT_METADATA_DESCRIPTION)); + g_queue_push_tail (&vis_list, vis); + } + } + gst_plugin_feature_list_free (features); + + vis_cookie = cookie; + + g_mutex_unlock (&vis_lock); +} + +/** + * gst_clapper_visualizations_get: + * + * Returns: (transfer full) (array zero-terminated=1) (element-type GstClapperVisualization): + * a %NULL terminated array containing all available + * visualizations. Use gst_clapper_visualizations_free() after + * usage. + */ +GstClapperVisualization ** +gst_clapper_visualizations_get (void) +{ + gint i = 0; + GList *l; + GstClapperVisualization **ret; + + gst_clapper_update_visualization_list (); + + g_mutex_lock (&vis_lock); + ret = g_new0 (GstClapperVisualization *, g_queue_get_length (&vis_list) + 1); + for (l = vis_list.head; l; l = l->next) + ret[i++] = gst_clapper_visualization_copy (l->data); + g_mutex_unlock (&vis_lock); + + return ret; +} diff --git a/lib/gst/clapper/gstclapper-visualization.h b/lib/gst/clapper/gstclapper-visualization.h new file mode 100644 index 00000000..f88f8b53 --- /dev/null +++ b/lib/gst/clapper/gstclapper-visualization.h @@ -0,0 +1,61 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_VISUALIZATION_H__ +#define __GST_CLAPPER_VISUALIZATION_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperVisualization GstClapperVisualization; +/** + * GstClapperVisualization: + * @name: name of the visualization. + * @description: description of the visualization. + * + * A #GstClapperVisualization descriptor. + */ +struct _GstClapperVisualization { + gchar *name; + gchar *description; +}; + +GST_CLAPPER_API +GType gst_clapper_visualization_get_type (void); + +GST_CLAPPER_API +GstClapperVisualization * gst_clapper_visualization_copy (const GstClapperVisualization *vis); + +GST_CLAPPER_API +void gst_clapper_visualization_free (GstClapperVisualization *vis); + +GST_CLAPPER_API +GstClapperVisualization ** gst_clapper_visualizations_get (void); + +GST_CLAPPER_API +void gst_clapper_visualizations_free (GstClapperVisualization **viss); + +G_END_DECLS + +#endif /* __GST_CLAPPER_VISUALIZATION_H__ */ diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c new file mode 100644 index 00000000..73256376 --- /dev/null +++ b/lib/gst/clapper/gstclapper.c @@ -0,0 +1,4789 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Brijesh Singh + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper + * @title: GstClapper + * @short_description: Clapper + * @symbols: + * - GstClapper + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include + +#include "gstclapper.h" +#include "gstclapper-signal-dispatcher-private.h" +#include "gstclapper-video-renderer-private.h" +#include "gstclapper-media-info-private.h" + +GST_DEBUG_CATEGORY_STATIC (gst_clapper_debug); +#define GST_CAT_DEFAULT gst_clapper_debug + +#define DEFAULT_URI NULL +#define DEFAULT_POSITION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE +#define DEFAULT_VOLUME 1.0 +#define DEFAULT_MUTE FALSE +#define DEFAULT_RATE 1.0 +#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100 +#define DEFAULT_AUDIO_VIDEO_OFFSET 0 +#define DEFAULT_SUBTITLE_VIDEO_OFFSET 0 + +/** + * gst_clapper_error_quark: + */ +GQuark +gst_clapper_error_quark (void) +{ + return g_quark_from_static_string ("gst-clapper-error-quark"); +} + +static GQuark QUARK_CONFIG; + +/* Keep ConfigQuarkId and _config_quark_strings ordered and synced */ +typedef enum +{ + CONFIG_QUARK_USER_AGENT = 0, + CONFIG_QUARK_POSITION_INTERVAL_UPDATE, + CONFIG_QUARK_ACCURATE_SEEK, + + CONFIG_QUARK_MAX +} ConfigQuarkId; + +static const gchar *_config_quark_strings[] = { + "user-agent", + "position-interval-update", + "accurate-seek", +}; + +GQuark _config_quark_table[CONFIG_QUARK_MAX]; + +#define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q] + +enum +{ + PROP_0, + PROP_VIDEO_RENDERER, + PROP_SIGNAL_DISPATCHER, + PROP_URI, + PROP_SUBURI, + PROP_POSITION, + PROP_DURATION, + PROP_MEDIA_INFO, + PROP_CURRENT_AUDIO_TRACK, + PROP_CURRENT_VIDEO_TRACK, + PROP_CURRENT_SUBTITLE_TRACK, + PROP_VOLUME, + PROP_MUTE, + PROP_RATE, + PROP_PIPELINE, + PROP_VIDEO_MULTIVIEW_MODE, + PROP_VIDEO_MULTIVIEW_FLAGS, + PROP_AUDIO_VIDEO_OFFSET, + PROP_SUBTITLE_VIDEO_OFFSET, + PROP_LAST +}; + +enum +{ + SIGNAL_URI_LOADED, + SIGNAL_POSITION_UPDATED, + SIGNAL_DURATION_CHANGED, + SIGNAL_STATE_CHANGED, + SIGNAL_BUFFERING, + SIGNAL_END_OF_STREAM, + SIGNAL_ERROR, + SIGNAL_WARNING, + SIGNAL_VIDEO_DIMENSIONS_CHANGED, + SIGNAL_MEDIA_INFO_UPDATED, + SIGNAL_VOLUME_CHANGED, + SIGNAL_MUTE_CHANGED, + SIGNAL_SEEK_DONE, + SIGNAL_LAST +}; + +enum +{ + GST_PLAY_FLAG_VIDEO = (1 << 0), + GST_PLAY_FLAG_AUDIO = (1 << 1), + GST_PLAY_FLAG_SUBTITLE = (1 << 2), + GST_PLAY_FLAG_VIS = (1 << 3) +}; + +struct _GstClapper +{ + GstObject parent; + + GstClapperVideoRenderer *video_renderer; + GstClapperSignalDispatcher *signal_dispatcher; + + gchar *uri; + gchar *redirect_uri; + gchar *suburi; + + GThread *thread; + GMutex lock; + GCond cond; + GMainContext *context; + GMainLoop *loop; + + GstElement *playbin; + GstBus *bus; + GstState target_state, current_state; + gboolean is_live, is_eos; + GSource *tick_source, *ready_timeout_source; + GstClockTime cached_duration; + + gdouble rate; + + GstClapperState app_state; + gint buffering; + + GstTagList *global_tags; + GstClapperMediaInfo *media_info; + + GstElement *current_vis_element; + + GstStructure *config; + + /* Protected by lock */ + gboolean seek_pending; /* Only set from main context */ + GstClockTime last_seek_time; /* Only set from main context */ + GSource *seek_source; + GstClockTime seek_position; + /* If TRUE, all signals are inhibited except the + * state-changed:GST_CLAPPER_STATE_STOPPED/PAUSED. This ensures that no signal + * is emitted after gst_clapper_stop/pause() has been called by the user. */ + gboolean inhibit_sigs; + + /* For playbin3 */ + gboolean use_playbin3; + GstStreamCollection *collection; + gchar *video_sid; + gchar *audio_sid; + gchar *subtitle_sid; + gulong stream_notify_id; +}; + +struct _GstClapperClass +{ + GstObjectClass parent_class; +}; + +#define parent_class gst_clapper_parent_class +G_DEFINE_TYPE (GstClapper, gst_clapper, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_clapper_dispose (GObject * object); +static void gst_clapper_finalize (GObject * object); +static void gst_clapper_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_clapper_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_clapper_constructed (GObject * object); + +static gpointer gst_clapper_main (gpointer data); + +static void gst_clapper_seek_internal_locked (GstClapper * self); +static void gst_clapper_stop_internal (GstClapper * self, gboolean transient); +static gboolean gst_clapper_pause_internal (gpointer user_data); +static gboolean gst_clapper_play_internal (gpointer user_data); +static gboolean gst_clapper_seek_internal (gpointer user_data); +static void gst_clapper_set_rate_internal (GstClapper * self); +static void change_state (GstClapper * self, GstClapperState state); + +static GstClapperMediaInfo *gst_clapper_media_info_create (GstClapper * self); + +static void gst_clapper_streams_info_create (GstClapper * self, + GstClapperMediaInfo * media_info, const gchar * prop, GType type); +static void gst_clapper_stream_info_update (GstClapper * self, + GstClapperStreamInfo * s); +static void gst_clapper_stream_info_update_tags_and_caps (GstClapper * self, + GstClapperStreamInfo * s); +static GstClapperStreamInfo *gst_clapper_stream_info_find (GstClapperMediaInfo * + media_info, GType type, gint stream_index); +static GstClapperStreamInfo *gst_clapper_stream_info_get_current (GstClapper * + self, const gchar * prop, GType type); + +static void gst_clapper_video_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info); +static void gst_clapper_audio_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info); +static void gst_clapper_subtitle_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info); + +/* For playbin3 */ +static void gst_clapper_streams_info_create_from_collection (GstClapper * self, + GstClapperMediaInfo * media_info, GstStreamCollection * collection); +static void gst_clapper_stream_info_update_from_stream (GstClapper * self, + GstClapperStreamInfo * s, GstStream * stream); +static GstClapperStreamInfo *gst_clapper_stream_info_find_from_stream_id + (GstClapperMediaInfo * media_info, const gchar * stream_id); +static GstClapperStreamInfo *gst_clapper_stream_info_get_current_from_stream_id + (GstClapper * self, const gchar * stream_id, GType type); +static void stream_notify_cb (GstStreamCollection * collection, + GstStream * stream, GParamSpec * pspec, GstClapper * self); + +static void emit_media_info_updated_signal (GstClapper * self); + +static void *get_title (GstTagList * tags); +static void *get_container_format (GstTagList * tags); +static void *get_from_tags (GstClapper * self, GstClapperMediaInfo * media_info, + void *(*func) (GstTagList *)); +static void *get_cover_sample (GstTagList * tags); + +static void remove_seek_source (GstClapper * self); + +static void +gst_clapper_init (GstClapper * self) +{ + GST_TRACE_OBJECT (self, "Initializing"); + + self = gst_clapper_get_instance_private (self); + + g_mutex_init (&self->lock); + g_cond_init (&self->cond); + + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + + /* *INDENT-OFF* */ + self->config = gst_structure_new_id (QUARK_CONFIG, + CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS, + CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE, + NULL); + /* *INDENT-ON* */ + + self->seek_pending = FALSE; + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + self->inhibit_sigs = FALSE; + + GST_TRACE_OBJECT (self, "Initialized"); +} + +static void +config_quark_initialize (void) +{ + gint i; + + QUARK_CONFIG = g_quark_from_static_string ("clapper-config"); + + if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX) + g_warning ("the quark table is not consistent! %d != %d", + (int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX); + + for (i = 0; i < CONFIG_QUARK_MAX; i++) { + _config_quark_table[i] = + g_quark_from_static_string (_config_quark_strings[i]); + } +} + +static void +gst_clapper_class_init (GstClapperClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_clapper_set_property; + gobject_class->get_property = gst_clapper_get_property; + gobject_class->dispose = gst_clapper_dispose; + gobject_class->finalize = gst_clapper_finalize; + gobject_class->constructed = gst_clapper_constructed; + + param_specs[PROP_VIDEO_RENDERER] = + g_param_spec_object ("video-renderer", + "Video Renderer", "Video renderer to use for rendering videos", + GST_TYPE_CLAPPER_VIDEO_RENDERER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SIGNAL_DISPATCHER] = + g_param_spec_object ("signal-dispatcher", + "Signal Dispatcher", "Dispatcher for the signals to e.g. event loops", + GST_TYPE_CLAPPER_SIGNAL_DISPATCHER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI", + DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI", + "Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_POSITION] = + g_param_spec_uint64 ("position", "Position", "Current Position", + 0, G_MAXUINT64, DEFAULT_POSITION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_MEDIA_INFO] = + g_param_spec_object ("media-info", "Media Info", + "Current media information", GST_TYPE_CLAPPER_MEDIA_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_CURRENT_AUDIO_TRACK] = + g_param_spec_object ("current-audio-track", "Current Audio Track", + "Current audio track information", GST_TYPE_CLAPPER_AUDIO_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_CURRENT_VIDEO_TRACK] = + g_param_spec_object ("current-video-track", "Current Video Track", + "Current video track information", GST_TYPE_CLAPPER_VIDEO_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_CURRENT_SUBTITLE_TRACK] = + g_param_spec_object ("current-subtitle-track", "Current Subtitle Track", + "Current audio subtitle information", GST_TYPE_CLAPPER_SUBTITLE_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "Duration", + 0, G_MAXUINT64, DEFAULT_DURATION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_VOLUME] = + g_param_spec_double ("volume", "Volume", "Volume", + 0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_MUTE] = + g_param_spec_boolean ("mute", "Mute", "Mute", + DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_PIPELINE] = + g_param_spec_object ("pipeline", "Pipeline", + "GStreamer pipeline that is used", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_RATE] = + g_param_spec_double ("rate", "rate", "Playback rate", + -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_VIDEO_MULTIVIEW_MODE] = + g_param_spec_enum ("video-multiview-mode", + "Multiview Mode Override", + "Re-interpret a video stream as one of several frame-packed stereoscopic modes.", + GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING, + GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] = + g_param_spec_flags ("video-multiview-flags", + "Multiview Flags Override", + "Override details of the multiview frame layout", + GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_AUDIO_VIDEO_OFFSET] = + g_param_spec_int64 ("audio-video-offset", "Audio Video Offset", + "The synchronisation offset between audio and video in nanoseconds", + G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SUBTITLE_VIDEO_OFFSET] = + g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset", + "The synchronisation offset between text and video in nanoseconds", + G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + signals[SIGNAL_URI_LOADED] = + g_signal_new ("uri-loaded", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[SIGNAL_POSITION_UPDATED] = + g_signal_new ("position-updated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_DURATION_CHANGED] = + g_signal_new ("duration-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_STATE_CHANGED] = + g_signal_new ("state-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_STATE); + + signals[SIGNAL_BUFFERING] = + g_signal_new ("buffering", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); + + signals[SIGNAL_END_OF_STREAM] = + g_signal_new ("end-of-stream", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); + + signals[SIGNAL_ERROR] = + g_signal_new ("error", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR); + + signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED] = + g_signal_new ("video-dimensions-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + + signals[SIGNAL_MEDIA_INFO_UPDATED] = + g_signal_new ("media-info-updated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_MEDIA_INFO); + + signals[SIGNAL_VOLUME_CHANGED] = + g_signal_new ("volume-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); + + signals[SIGNAL_MUTE_CHANGED] = + g_signal_new ("mute-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); + + signals[SIGNAL_WARNING] = + g_signal_new ("warning", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR); + + signals[SIGNAL_SEEK_DONE] = + g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + config_quark_initialize (); +} + +static void +gst_clapper_dispose (GObject * object) +{ + GstClapper *self = GST_CLAPPER (object); + + GST_TRACE_OBJECT (self, "Stopping main thread"); + + if (self->loop) { + g_main_loop_quit (self->loop); + + if (self->thread != g_thread_self ()) + g_thread_join (self->thread); + else + g_thread_unref (self->thread); + self->thread = NULL; + + g_main_loop_unref (self->loop); + self->loop = NULL; + + g_main_context_unref (self->context); + self->context = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_clapper_finalize (GObject * object) +{ + GstClapper *self = GST_CLAPPER (object); + + GST_TRACE_OBJECT (self, "Finalizing"); + + g_free (self->uri); + g_free (self->redirect_uri); + g_free (self->suburi); + g_free (self->video_sid); + g_free (self->audio_sid); + g_free (self->subtitle_sid); + if (self->global_tags) + gst_tag_list_unref (self->global_tags); + if (self->video_renderer) + g_object_unref (self->video_renderer); + if (self->signal_dispatcher) + g_object_unref (self->signal_dispatcher); + if (self->current_vis_element) + gst_object_unref (self->current_vis_element); + if (self->config) + gst_structure_free (self->config); + if (self->collection) + gst_object_unref (self->collection); + g_mutex_clear (&self->lock); + g_cond_clear (&self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_clapper_constructed (GObject * object) +{ + GstClapper *self = GST_CLAPPER (object); + + GST_TRACE_OBJECT (self, "Constructed"); + + g_mutex_lock (&self->lock); + self->thread = g_thread_new ("GstClapper", gst_clapper_main, self); + while (!self->loop || !g_main_loop_is_running (self->loop)) + g_cond_wait (&self->cond, &self->lock); + g_mutex_unlock (&self->lock); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +typedef struct +{ + GstClapper *clapper; + gchar *uri; +} UriLoadedSignalData; + +static void +uri_loaded_dispatch (gpointer user_data) +{ + UriLoadedSignalData *data = user_data; + + g_signal_emit (data->clapper, signals[SIGNAL_URI_LOADED], 0, data->uri); +} + +static void +uri_loaded_signal_data_free (UriLoadedSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data->uri); + g_free (data); +} + +static gboolean +gst_clapper_set_uri_internal (gpointer user_data) +{ + GstClapper *self = user_data; + + gst_clapper_stop_internal (self, FALSE); + + g_mutex_lock (&self->lock); + + GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri)); + + g_object_set (self->playbin, "uri", self->uri, NULL); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_URI_LOADED], 0, NULL, NULL, NULL) != 0) { + UriLoadedSignalData *data = g_new (UriLoadedSignalData, 1); + + data->clapper = g_object_ref (self); + data->uri = g_strdup (self->uri); + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + uri_loaded_dispatch, data, + (GDestroyNotify) uri_loaded_signal_data_free); + } + + g_object_set (self->playbin, "suburi", NULL, NULL); + + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +static gboolean +gst_clapper_set_suburi_internal (gpointer user_data) +{ + GstClapper *self = user_data; + GstClockTime position; + GstState target_state; + + /* save the state and position */ + target_state = self->target_state; + position = gst_clapper_get_position (self); + + gst_clapper_stop_internal (self, TRUE); + g_mutex_lock (&self->lock); + + GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'", + GST_STR_NULL (self->suburi)); + + g_object_set (self->playbin, "suburi", self->suburi, NULL); + + g_mutex_unlock (&self->lock); + + /* restore state and position */ + if (position != GST_CLOCK_TIME_NONE) + gst_clapper_seek (self, position); + if (target_state == GST_STATE_PAUSED) + gst_clapper_pause_internal (self); + else if (target_state == GST_STATE_PLAYING) + gst_clapper_play_internal (self); + + return G_SOURCE_REMOVE; +} + +static void +gst_clapper_set_rate_internal (GstClapper * self) +{ + self->seek_position = gst_clapper_get_position (self); + + /* If there is no seek being dispatch to the main context currently do that, + * otherwise we just updated the rate so that it will be taken by + * the seek handler from the main context instead of the old one. + */ + if (!self->seek_source) { + /* If no seek is pending then create new seek source */ + if (!self->seek_pending) { + self->seek_source = g_idle_source_new (); + g_source_set_callback (self->seek_source, + (GSourceFunc) gst_clapper_seek_internal, self, NULL); + g_source_attach (self->seek_source, self->context); + } + } +} + +static void +gst_clapper_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstClapper *self = GST_CLAPPER (object); + + switch (prop_id) { + case PROP_VIDEO_RENDERER: + self->video_renderer = g_value_dup_object (value); + break; + case PROP_SIGNAL_DISPATCHER: + self->signal_dispatcher = g_value_dup_object (value); + break; + case PROP_URI:{ + g_mutex_lock (&self->lock); + g_free (self->uri); + g_free (self->redirect_uri); + self->redirect_uri = NULL; + + g_free (self->suburi); + self->suburi = NULL; + + self->uri = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set uri=%s", self->uri); + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_clapper_set_uri_internal, self, NULL); + break; + } + case PROP_SUBURI:{ + g_mutex_lock (&self->lock); + g_free (self->suburi); + + self->suburi = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi); + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_clapper_set_suburi_internal, self, NULL); + break; + } + case PROP_VOLUME: + GST_DEBUG_OBJECT (self, "Set volume=%lf", g_value_get_double (value)); + g_object_set_property (G_OBJECT (self->playbin), "volume", value); + break; + case PROP_RATE: + g_mutex_lock (&self->lock); + self->rate = g_value_get_double (value); + GST_DEBUG_OBJECT (self, "Set rate=%lf", g_value_get_double (value)); + gst_clapper_set_rate_internal (self); + g_mutex_unlock (&self->lock); + break; + case PROP_MUTE: + GST_DEBUG_OBJECT (self, "Set mute=%d", g_value_get_boolean (value)); + g_object_set_property (G_OBJECT (self->playbin), "mute", value); + break; + case PROP_VIDEO_MULTIVIEW_MODE: + GST_DEBUG_OBJECT (self, "Set multiview mode=%u", + g_value_get_enum (value)); + g_object_set_property (G_OBJECT (self->playbin), "video-multiview-mode", + value); + break; + case PROP_VIDEO_MULTIVIEW_FLAGS: + GST_DEBUG_OBJECT (self, "Set multiview flags=%x", + g_value_get_flags (value)); + g_object_set_property (G_OBJECT (self->playbin), "video-multiview-flags", + value); + break; + case PROP_AUDIO_VIDEO_OFFSET: + g_object_set_property (G_OBJECT (self->playbin), "av-offset", value); + break; + case PROP_SUBTITLE_VIDEO_OFFSET: + g_object_set_property (G_OBJECT (self->playbin), "text-offset", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstClapper *self = GST_CLAPPER (object); + + switch (prop_id) { + case PROP_URI: + g_mutex_lock (&self->lock); + g_value_set_string (value, self->uri); + g_mutex_unlock (&self->lock); + break; + case PROP_SUBURI: + g_mutex_lock (&self->lock); + g_value_set_string (value, self->suburi); + g_mutex_unlock (&self->lock); + GST_DEBUG_OBJECT (self, "Returning suburi=%s", + g_value_get_string (value)); + break; + case PROP_POSITION:{ + gint64 position = GST_CLOCK_TIME_NONE; + + gst_element_query_position (self->playbin, GST_FORMAT_TIME, &position); + g_value_set_uint64 (value, position); + GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_DURATION:{ + g_value_set_uint64 (value, self->cached_duration); + GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_MEDIA_INFO:{ + GstClapperMediaInfo *media_info = gst_clapper_get_media_info (self); + g_value_take_object (value, media_info); + break; + } + case PROP_CURRENT_AUDIO_TRACK:{ + GstClapperAudioInfo *audio_info = + gst_clapper_get_current_audio_track (self); + g_value_take_object (value, audio_info); + break; + } + case PROP_CURRENT_VIDEO_TRACK:{ + GstClapperVideoInfo *video_info = + gst_clapper_get_current_video_track (self); + g_value_take_object (value, video_info); + break; + } + case PROP_CURRENT_SUBTITLE_TRACK:{ + GstClapperSubtitleInfo *subtitle_info = + gst_clapper_get_current_subtitle_track (self); + g_value_take_object (value, subtitle_info); + break; + } + case PROP_VOLUME: + g_object_get_property (G_OBJECT (self->playbin), "volume", value); + GST_TRACE_OBJECT (self, "Returning volume=%lf", + g_value_get_double (value)); + break; + case PROP_RATE: + g_mutex_lock (&self->lock); + g_value_set_double (value, self->rate); + g_mutex_unlock (&self->lock); + break; + case PROP_MUTE: + g_object_get_property (G_OBJECT (self->playbin), "mute", value); + GST_TRACE_OBJECT (self, "Returning mute=%d", g_value_get_boolean (value)); + break; + case PROP_PIPELINE: + g_value_set_object (value, self->playbin); + break; + case PROP_VIDEO_MULTIVIEW_MODE:{ + g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode", + value); + GST_TRACE_OBJECT (self, "Return multiview mode=%d", + g_value_get_enum (value)); + break; + } + case PROP_VIDEO_MULTIVIEW_FLAGS:{ + g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags", + value); + GST_TRACE_OBJECT (self, "Return multiview flags=%x", + g_value_get_flags (value)); + break; + } + case PROP_AUDIO_VIDEO_OFFSET: + g_object_get_property (G_OBJECT (self->playbin), "av-offset", value); + break; + case PROP_SUBTITLE_VIDEO_OFFSET: + g_object_get_property (G_OBJECT (self->playbin), "text-offset", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +main_loop_running_cb (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + GST_TRACE_OBJECT (self, "Main loop running now"); + + g_mutex_lock (&self->lock); + g_cond_signal (&self->cond); + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +typedef struct +{ + GstClapper *clapper; + GstClapperState state; +} StateChangedSignalData; + +static void +state_changed_dispatch (gpointer user_data) +{ + StateChangedSignalData *data = user_data; + + if (data->clapper->inhibit_sigs && data->state != GST_CLAPPER_STATE_STOPPED + && data->state != GST_CLAPPER_STATE_PAUSED) + return; + + g_signal_emit (data->clapper, signals[SIGNAL_STATE_CHANGED], 0, data->state); +} + +static void +state_changed_signal_data_free (StateChangedSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static void +change_state (GstClapper * self, GstClapperState state) +{ + if (state == self->app_state) + return; + + GST_DEBUG_OBJECT (self, "Changing app state from %s to %s", + gst_clapper_state_get_name (self->app_state), + gst_clapper_state_get_name (state)); + self->app_state = state; + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_STATE_CHANGED], 0, NULL, NULL, NULL) != 0) { + StateChangedSignalData *data = g_new (StateChangedSignalData, 1); + + data->clapper = g_object_ref (self); + data->state = state; + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + state_changed_dispatch, data, + (GDestroyNotify) state_changed_signal_data_free); + } +} + +typedef struct +{ + GstClapper *clapper; + GstClockTime position; +} PositionUpdatedSignalData; + +static void +position_updated_dispatch (gpointer user_data) +{ + PositionUpdatedSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + if (data->clapper->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->clapper, signals[SIGNAL_POSITION_UPDATED], 0, + data->position); + g_object_notify_by_pspec (G_OBJECT (data->clapper), + param_specs[PROP_POSITION]); + } +} + +static void +position_updated_signal_data_free (PositionUpdatedSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static gboolean +tick_cb (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + gint64 position; + + if (self->target_state >= GST_STATE_PAUSED + && gst_element_query_position (self->playbin, GST_FORMAT_TIME, + &position)) { + GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_POSITION_UPDATED], 0, NULL, NULL, NULL) != 0) { + PositionUpdatedSignalData *data = g_new (PositionUpdatedSignalData, 1); + + data->clapper = g_object_ref (self); + data->position = position; + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + position_updated_dispatch, data, + (GDestroyNotify) position_updated_signal_data_free); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +add_tick_source (GstClapper * self) +{ + guint position_update_interval_ms; + + if (self->tick_source) + return; + + position_update_interval_ms = + gst_clapper_config_get_position_update_interval (self->config); + if (!position_update_interval_ms) + return; + + self->tick_source = g_timeout_source_new (position_update_interval_ms); + g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL); + g_source_attach (self->tick_source, self->context); +} + +static void +remove_tick_source (GstClapper * self) +{ + if (!self->tick_source) + return; + + g_source_destroy (self->tick_source); + g_source_unref (self->tick_source); + self->tick_source = NULL; +} + +static gboolean +ready_timeout_cb (gpointer user_data) +{ + GstClapper *self = user_data; + + if (self->target_state <= GST_STATE_READY) { + GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state"); + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + gst_element_set_state (self->playbin, GST_STATE_NULL); + } + + return G_SOURCE_REMOVE; +} + +static void +add_ready_timeout_source (GstClapper * self) +{ + if (self->ready_timeout_source) + return; + + self->ready_timeout_source = g_timeout_source_new_seconds (60); + g_source_set_callback (self->ready_timeout_source, + (GSourceFunc) ready_timeout_cb, self, NULL); + g_source_attach (self->ready_timeout_source, self->context); +} + +static void +remove_ready_timeout_source (GstClapper * self) +{ + if (!self->ready_timeout_source) + return; + + g_source_destroy (self->ready_timeout_source); + g_source_unref (self->ready_timeout_source); + self->ready_timeout_source = NULL; +} + +typedef struct +{ + GstClapper *clapper; + GError *err; +} ErrorSignalData; + +static void +error_dispatch (gpointer user_data) +{ + ErrorSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + g_signal_emit (data->clapper, signals[SIGNAL_ERROR], 0, data->err); +} + +static void +free_error_signal_data (ErrorSignalData * data) +{ + g_object_unref (data->clapper); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_error (GstClapper * self, GError * err) +{ + GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message, + g_quark_to_string (err->domain), err->code); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_ERROR], 0, NULL, NULL, NULL) != 0) { + ErrorSignalData *data = g_new (ErrorSignalData, 1); + + data->clapper = g_object_ref (self); + data->err = g_error_copy (err); + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + error_dispatch, data, (GDestroyNotify) free_error_signal_data); + } + + g_error_free (err); + + remove_tick_source (self); + remove_ready_timeout_source (self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + self->is_live = FALSE; + self->is_eos = FALSE; + gst_element_set_state (self->playbin, GST_STATE_NULL); + change_state (self, GST_CLAPPER_STATE_STOPPED); + self->buffering = 100; + + g_mutex_lock (&self->lock); + if (self->media_info) { + g_object_unref (self->media_info); + self->media_info = NULL; + } + + if (self->global_tags) { + gst_tag_list_unref (self->global_tags); + self->global_tags = NULL; + } + + self->seek_pending = FALSE; + remove_seek_source (self); + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + g_mutex_unlock (&self->lock); +} + +static void +dump_dot_file (GstClapper * self, const gchar * name) +{ + gchar *full_name; + + full_name = g_strdup_printf ("gst-clapper.%p.%s", self, name); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->playbin), + GST_DEBUG_GRAPH_SHOW_ALL, full_name); + + g_free (full_name); +} + +typedef struct +{ + GstClapper *clapper; + GError *err; +} WarningSignalData; + +static void +warning_dispatch (gpointer user_data) +{ + WarningSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + g_signal_emit (data->clapper, signals[SIGNAL_WARNING], 0, data->err); +} + +static void +free_warning_signal_data (WarningSignalData * data) +{ + g_object_unref (data->clapper); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_warning (GstClapper * self, GError * err) +{ + GST_ERROR_OBJECT (self, "Warning: %s (%s, %d)", err->message, + g_quark_to_string (err->domain), err->code); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_WARNING], 0, NULL, NULL, NULL) != 0) { + WarningSignalData *data = g_new (WarningSignalData, 1); + + data->clapper = g_object_ref (self); + data->err = g_error_copy (err); + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + warning_dispatch, data, (GDestroyNotify) free_warning_signal_data); + } + + g_error_free (err); +} + +static void +error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GError *err, *clapper_err; + gchar *name, *debug, *message, *full_message; + + dump_dot_file (self, "error"); + + gst_message_parse_error (msg, &err, &debug); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + if (debug) + full_message = + g_strdup_printf ("Error from element %s: %s\n%s\n%s", name, message, + err->message, debug); + else + full_message = + g_strdup_printf ("Error from element %s: %s\n%s", name, message, + err->message); + + GST_ERROR_OBJECT (self, "ERROR: from element %s: %s", name, err->message); + if (debug != NULL) + GST_ERROR_OBJECT (self, "Additional debug info: %s", debug); + + clapper_err = + g_error_new_literal (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + full_message); + emit_error (self, clapper_err); + + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (full_message); + g_free (message); +} + +static void +warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GError *err, *clapper_err; + gchar *name, *debug, *message, *full_message; + + dump_dot_file (self, "warning"); + + gst_message_parse_warning (msg, &err, &debug); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + if (debug) + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message, + err->message, debug); + else + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s", name, message, + err->message); + + GST_WARNING_OBJECT (self, "WARNING: from element %s: %s", name, err->message); + if (debug != NULL) + GST_WARNING_OBJECT (self, "Additional debug info: %s", debug); + + clapper_err = + g_error_new_literal (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + full_message); + emit_warning (self, clapper_err); + + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (full_message); + g_free (message); +} + +static void +eos_dispatch (gpointer user_data) +{ + GstClapper *clapper = user_data; + + if (clapper->inhibit_sigs) + return; + + g_signal_emit (clapper, signals[SIGNAL_END_OF_STREAM], 0); +} + +static void +eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + GST_DEBUG_OBJECT (self, "End of stream"); + + tick_cb (self); + remove_tick_source (self); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_END_OF_STREAM], 0, NULL, NULL, NULL) != 0) { + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref); + } + change_state (self, GST_CLAPPER_STATE_STOPPED); + self->buffering = 100; + self->is_eos = TRUE; +} + +typedef struct +{ + GstClapper *clapper; + gint percent; +} BufferingSignalData; + +static void +buffering_dispatch (gpointer user_data) +{ + BufferingSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + if (data->clapper->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->clapper, signals[SIGNAL_BUFFERING], 0, data->percent); + } +} + +static void +buffering_signal_data_free (BufferingSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static void +buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + gint percent; + + if (self->target_state < GST_STATE_PAUSED) + return; + if (self->is_live) + return; + + gst_message_parse_buffering (msg, &percent); + GST_LOG_OBJECT (self, "Buffering %d%%", percent); + + if (percent < 100 && self->target_state >= GST_STATE_PAUSED) { + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Waiting for buffering to finish"); + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to handle buffering")); + return; + } + + change_state (self, GST_CLAPPER_STATE_BUFFERING); + } + + if (self->buffering != percent) { + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_BUFFERING], 0, NULL, NULL, NULL) != 0) { + BufferingSignalData *data = g_new (BufferingSignalData, 1); + + data->clapper = g_object_ref (self); + data->percent = percent; + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + buffering_dispatch, data, + (GDestroyNotify) buffering_signal_data_free); + } + + self->buffering = percent; + } + + + g_mutex_lock (&self->lock); + if (percent == 100 && (self->seek_position != GST_CLOCK_TIME_NONE || + self->seek_pending)) { + g_mutex_unlock (&self->lock); + + GST_DEBUG_OBJECT (self, "Buffering finished - seek pending"); + } else if (percent == 100 && self->target_state >= GST_STATE_PLAYING + && self->current_state >= GST_STATE_PAUSED) { + GstStateChangeReturn state_ret; + + g_mutex_unlock (&self->lock); + + GST_DEBUG_OBJECT (self, "Buffering finished - going to PLAYING"); + state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING); + /* Application state change is happening when the state change happened */ + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to handle buffering")); + } else if (percent == 100 && self->target_state >= GST_STATE_PAUSED) { + g_mutex_unlock (&self->lock); + + GST_DEBUG_OBJECT (self, "Buffering finished - staying PAUSED"); + change_state (self, GST_CLAPPER_STATE_PAUSED); + } else { + g_mutex_unlock (&self->lock); + } +} + +static void +clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Clock lost"); + if (self->target_state >= GST_STATE_PLAYING) { + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + if (state_ret != GST_STATE_CHANGE_FAILURE) + state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING); + + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to handle clock loss")); + } +} + +typedef struct +{ + GstClapper *clapper; + gint width, height; +} VideoDimensionsChangedSignalData; + +static void +video_dimensions_changed_dispatch (gpointer user_data) +{ + VideoDimensionsChangedSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + if (data->clapper->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->clapper, signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0, + data->width, data->height); + } +} + +static void +video_dimensions_changed_signal_data_free (VideoDimensionsChangedSignalData * + data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static void +check_video_dimensions_changed (GstClapper * self) +{ + GstElement *video_sink; + GstPad *video_sink_pad; + GstCaps *caps; + GstVideoInfo info; + gint width = 0, height = 0; + + g_object_get (self->playbin, "video-sink", &video_sink, NULL); + if (!video_sink) + goto out; + + video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); + if (!video_sink_pad) { + gst_object_unref (video_sink); + goto out; + } + + caps = gst_pad_get_current_caps (video_sink_pad); + + if (caps) { + if (gst_video_info_from_caps (&info, caps)) { + info.width = info.width * info.par_n / info.par_d; + + GST_DEBUG_OBJECT (self, "Video dimensions changed: %dx%d", info.width, + info.height); + width = info.width; + height = info.height; + } + + gst_caps_unref (caps); + } + gst_object_unref (video_sink_pad); + gst_object_unref (video_sink); + +out: + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0, NULL, NULL, NULL) != 0) { + VideoDimensionsChangedSignalData *data = + g_new (VideoDimensionsChangedSignalData, 1); + + data->clapper = g_object_ref (self); + data->width = width; + data->height = height; + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + video_dimensions_changed_dispatch, data, + (GDestroyNotify) video_dimensions_changed_signal_data_free); + } +} + +static void +notify_caps_cb (G_GNUC_UNUSED GObject * object, + G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + check_video_dimensions_changed (self); +} + +typedef struct +{ + GstClapper *clapper; + GstClockTime duration; +} DurationChangedSignalData; + +static void +duration_changed_dispatch (gpointer user_data) +{ + DurationChangedSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + if (data->clapper->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->clapper, signals[SIGNAL_DURATION_CHANGED], 0, + data->duration); + g_object_notify_by_pspec (G_OBJECT (data->clapper), + param_specs[PROP_DURATION]); + } +} + +static void +duration_changed_signal_data_free (DurationChangedSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static void +emit_duration_changed (GstClapper * self, GstClockTime duration) +{ + gboolean updated = FALSE; + + if (self->cached_duration == duration) + return; + + GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + + self->cached_duration = duration; + g_mutex_lock (&self->lock); + if (self->media_info) { + self->media_info->duration = duration; + updated = TRUE; + } + g_mutex_unlock (&self->lock); + if (updated) { + emit_media_info_updated_signal (self); + } + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) { + DurationChangedSignalData *data = g_new (DurationChangedSignalData, 1); + + data->clapper = g_object_ref (self); + data->duration = duration; + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + duration_changed_dispatch, data, + (GDestroyNotify) duration_changed_signal_data_free); + } +} + +typedef struct +{ + GstClapper *clapper; + GstClockTime position; +} SeekDoneSignalData; + +static void +seek_done_dispatch (gpointer user_data) +{ + SeekDoneSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + g_signal_emit (data->clapper, signals[SIGNAL_SEEK_DONE], 0, data->position); +} + +static void +seek_done_signal_data_free (SeekDoneSignalData * data) +{ + g_object_unref (data->clapper); + g_free (data); +} + +static void +emit_seek_done (GstClapper * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_SEEK_DONE], 0, NULL, NULL, NULL) != 0) { + SeekDoneSignalData *data = g_new (SeekDoneSignalData, 1); + + data->clapper = g_object_ref (self); + data->position = gst_clapper_get_position (self); + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + seek_done_dispatch, data, (GDestroyNotify) seek_done_signal_data_free); + } +} + +static void +state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstState old_state, new_state, pending_state; + + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->playbin)) { + gchar *transition_name; + + GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state), + gst_element_state_get_name (pending_state)); + + transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + dump_dot_file (self, transition_name); + g_free (transition_name); + + self->current_state = new_state; + + if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED + && pending_state == GST_STATE_VOID_PENDING) { + GstElement *video_sink; + GstPad *video_sink_pad; + gint64 duration = -1; + + GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled"); + + g_mutex_lock (&self->lock); + if (self->media_info) + g_object_unref (self->media_info); + self->media_info = gst_clapper_media_info_create (self); + g_mutex_unlock (&self->lock); + emit_media_info_updated_signal (self); + + g_object_get (self->playbin, "video-sink", &video_sink, NULL); + + if (video_sink) { + video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); + + if (video_sink_pad) { + g_signal_connect (video_sink_pad, "notify::caps", + (GCallback) notify_caps_cb, self); + gst_object_unref (video_sink_pad); + } + gst_object_unref (video_sink); + } + + check_video_dimensions_changed (self); + if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, + &duration)) { + emit_duration_changed (self, duration); + } else { + self->cached_duration = GST_CLOCK_TIME_NONE; + } + } + + if (new_state == GST_STATE_PAUSED + && pending_state == GST_STATE_VOID_PENDING) { + remove_tick_source (self); + + g_mutex_lock (&self->lock); + if (self->seek_pending) { + self->seek_pending = FALSE; + + if (!self->media_info->seekable) { + GST_DEBUG_OBJECT (self, "Media is not seekable"); + remove_seek_source (self); + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + } else if (self->seek_source) { + GST_DEBUG_OBJECT (self, "Seek finished but new seek is pending"); + gst_clapper_seek_internal_locked (self); + } else { + GST_DEBUG_OBJECT (self, "Seek finished"); + emit_seek_done (self); + } + } + + if (self->seek_position != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state"); + gst_clapper_seek_internal_locked (self); + g_mutex_unlock (&self->lock); + } else if (!self->seek_pending) { + g_mutex_unlock (&self->lock); + + tick_cb (self); + + if (self->target_state >= GST_STATE_PLAYING && self->buffering == 100) { + GstStateChangeReturn state_ret; + + state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING); + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_CLAPPER_ERROR, + GST_CLAPPER_ERROR_FAILED, "Failed to play")); + } else if (self->buffering == 100) { + change_state (self, GST_CLAPPER_STATE_PAUSED); + } + } else { + g_mutex_unlock (&self->lock); + } + } else if (new_state == GST_STATE_PLAYING + && pending_state == GST_STATE_VOID_PENDING) { + + /* If no seek is currently pending, add the tick source. This can happen + * if we seeked already but the state-change message was still queued up */ + if (!self->seek_pending) { + add_tick_source (self); + change_state (self, GST_CLAPPER_STATE_PLAYING); + } + } else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) { + change_state (self, GST_CLAPPER_STATE_STOPPED); + } else { + /* Otherwise we neither reached PLAYING nor PAUSED, so must + * wait for something to happen... i.e. are BUFFERING now */ + change_state (self, GST_CLAPPER_STATE_BUFFERING); + } + } +} + +static void +duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + gint64 duration = GST_CLOCK_TIME_NONE; + + if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) { + emit_duration_changed (self, duration); + } +} + +static void +latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + GST_DEBUG_OBJECT (self, "Latency changed"); + + gst_bin_recalculate_latency (GST_BIN (self->playbin)); +} + +static void +request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstState state; + GstStateChangeReturn state_ret; + + gst_message_parse_request_state (msg, &state); + + GST_DEBUG_OBJECT (self, "State %s requested", + gst_element_state_get_name (state)); + + self->target_state = state; + state_ret = gst_element_set_state (self->playbin, state); + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to change to requested state %s", + gst_element_state_get_name (state))); +} + +static void +media_info_update (GstClapper * self, GstClapperMediaInfo * info) +{ + g_free (info->title); + info->title = get_from_tags (self, info, get_title); + + g_free (info->container); + info->container = get_from_tags (self, info, get_container_format); + + if (info->image_sample) + gst_sample_unref (info->image_sample); + info->image_sample = get_from_tags (self, info, get_cover_sample); + + GST_DEBUG_OBJECT (self, "title: %s, container: %s " + "image_sample: %p", info->title, info->container, info->image_sample); +} + +static void +tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstTagList *tags = NULL; + + gst_message_parse_tag (msg, &tags); + + GST_DEBUG_OBJECT (self, "received %s tags", + gst_tag_list_get_scope (tags) == + GST_TAG_SCOPE_GLOBAL ? "global" : "stream"); + + if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) { + g_mutex_lock (&self->lock); + if (self->media_info) { + if (self->media_info->tags) + gst_tag_list_unref (self->media_info->tags); + self->media_info->tags = gst_tag_list_ref (tags); + media_info_update (self, self->media_info); + g_mutex_unlock (&self->lock); + emit_media_info_updated_signal (self); + } else { + if (self->global_tags) + gst_tag_list_unref (self->global_tags); + self->global_tags = gst_tag_list_ref (tags); + g_mutex_unlock (&self->lock); + } + } + + gst_tag_list_unref (tags); +} + +static void +element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + const GstStructure *s; + + s = gst_message_get_structure (msg); + if (gst_structure_has_name (s, "redirect")) { + const gchar *new_location; + + new_location = gst_structure_get_string (s, "new-location"); + if (!new_location) { + const GValue *locations_list, *location_val; + guint i, size; + + locations_list = gst_structure_get_value (s, "locations"); + size = gst_value_list_get_size (locations_list); + for (i = 0; i < size; ++i) { + const GstStructure *location_s; + + location_val = gst_value_list_get_value (locations_list, i); + if (!GST_VALUE_HOLDS_STRUCTURE (location_val)) + continue; + + location_s = (const GstStructure *) g_value_get_boxed (location_val); + if (!gst_structure_has_name (location_s, "redirect")) + continue; + + new_location = gst_structure_get_string (location_s, "new-location"); + if (new_location) + break; + } + } + + if (new_location) { + GstState target_state; + + GST_DEBUG_OBJECT (self, "Redirect to '%s'", new_location); + + /* Remember target state and restore after setting the URI */ + target_state = self->target_state; + + gst_clapper_stop_internal (self, TRUE); + + g_mutex_lock (&self->lock); + g_free (self->redirect_uri); + self->redirect_uri = g_strdup (new_location); + g_object_set (self->playbin, "uri", self->redirect_uri, NULL); + g_mutex_unlock (&self->lock); + + if (target_state == GST_STATE_PAUSED) + gst_clapper_pause_internal (self); + else if (target_state == GST_STATE_PLAYING) + gst_clapper_play_internal (self); + } + } +} + +/* Must be called with lock */ +static gboolean +update_stream_collection (GstClapper * self, GstStreamCollection * collection) +{ + if (self->collection && self->collection == collection) + return FALSE; + + if (self->collection && self->stream_notify_id) + g_signal_handler_disconnect (self->collection, self->stream_notify_id); + + gst_object_replace ((GstObject **) & self->collection, + (GstObject *) collection); + if (self->media_info) { + gst_object_unref (self->media_info); + self->media_info = gst_clapper_media_info_create (self); + } + + self->stream_notify_id = + g_signal_connect (self->collection, "stream-notify", + G_CALLBACK (stream_notify_cb), self); + + return TRUE; +} + +static void +stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstStreamCollection *collection = NULL; + gboolean updated = FALSE; + + gst_message_parse_stream_collection (msg, &collection); + + if (!collection) + return; + + g_mutex_lock (&self->lock); + updated = update_stream_collection (self, collection); + gst_object_unref (collection); + g_mutex_unlock (&self->lock); + + if (self->media_info && updated) + emit_media_info_updated_signal (self); +} + +static void +streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstStreamCollection *collection = NULL; + gboolean updated = FALSE; + guint i, len; + + gst_message_parse_streams_selected (msg, &collection); + + if (!collection) + return; + + g_mutex_lock (&self->lock); + updated = update_stream_collection (self, collection); + gst_object_unref (collection); + + g_free (self->video_sid); + g_free (self->audio_sid); + g_free (self->subtitle_sid); + self->video_sid = NULL; + self->audio_sid = NULL; + self->subtitle_sid = NULL; + + len = gst_message_streams_selected_get_size (msg); + for (i = 0; i < len; i++) { + GstStream *stream; + GstStreamType stream_type; + const gchar *stream_id; + gchar **current_sid; + stream = gst_message_streams_selected_get_stream (msg, i); + stream_type = gst_stream_get_stream_type (stream); + stream_id = gst_stream_get_stream_id (stream); + if (stream_type & GST_STREAM_TYPE_AUDIO) + current_sid = &self->audio_sid; + else if (stream_type & GST_STREAM_TYPE_VIDEO) + current_sid = &self->video_sid; + else if (stream_type & GST_STREAM_TYPE_TEXT) + current_sid = &self->subtitle_sid; + else { + GST_WARNING_OBJECT (self, + "Unknown stream-id %s with type 0x%x", stream_id, stream_type); + continue; + } + + if (G_UNLIKELY (*current_sid)) { + GST_FIXME_OBJECT (self, + "Multiple streams are selected for type %s, choose the first one", + gst_stream_type_get_name (stream_type)); + continue; + } + + *current_sid = g_strdup (stream_id); + } + g_mutex_unlock (&self->lock); + + if (self->media_info && updated) + emit_media_info_updated_signal (self); +} + +static void +clapper_set_flag (GstClapper * self, gint pos) +{ + gint flags; + + g_object_get (self->playbin, "flags", &flags, NULL); + flags |= pos; + g_object_set (self->playbin, "flags", flags, NULL); + + GST_DEBUG_OBJECT (self, "setting flags=%#x", flags); +} + +static void +clapper_clear_flag (GstClapper * self, gint pos) +{ + gint flags; + + g_object_get (self->playbin, "flags", &flags, NULL); + flags &= ~pos; + g_object_set (self->playbin, "flags", flags, NULL); + + GST_DEBUG_OBJECT (self, "setting flags=%#x", flags); +} + +typedef struct +{ + GstClapper *clapper; + GstClapperMediaInfo *info; +} MediaInfoUpdatedSignalData; + +static void +media_info_updated_dispatch (gpointer user_data) +{ + MediaInfoUpdatedSignalData *data = user_data; + + if (data->clapper->inhibit_sigs) + return; + + if (data->clapper->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->clapper, signals[SIGNAL_MEDIA_INFO_UPDATED], 0, + data->info); + } +} + +static void +free_media_info_updated_signal_data (MediaInfoUpdatedSignalData * data) +{ + g_object_unref (data->clapper); + g_object_unref (data->info); + g_free (data); +} + +/* + * emit_media_info_updated_signal: + * + * create a new copy of self->media_info object and emits the newly created + * copy to user application. The newly created media_info will be unref'ed + * as part of signal finalize method. + */ +static void +emit_media_info_updated_signal (GstClapper * self) +{ + MediaInfoUpdatedSignalData *data = g_new (MediaInfoUpdatedSignalData, 1); + data->clapper = g_object_ref (self); + g_mutex_lock (&self->lock); + data->info = gst_clapper_media_info_copy (self->media_info); + g_mutex_unlock (&self->lock); + + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + media_info_updated_dispatch, data, + (GDestroyNotify) free_media_info_updated_signal_data); +} + +static GstCaps * +get_caps (GstClapper * self, gint stream_index, GType type) +{ + GstPad *pad = NULL; + GstCaps *caps = NULL; + + if (type == GST_TYPE_CLAPPER_VIDEO_INFO) + g_signal_emit_by_name (G_OBJECT (self->playbin), + "get-video-pad", stream_index, &pad); + else if (type == GST_TYPE_CLAPPER_AUDIO_INFO) + g_signal_emit_by_name (G_OBJECT (self->playbin), + "get-audio-pad", stream_index, &pad); + else + g_signal_emit_by_name (G_OBJECT (self->playbin), + "get-text-pad", stream_index, &pad); + + if (pad) { + caps = gst_pad_get_current_caps (pad); + gst_object_unref (pad); + } + + return caps; +} + +static void +gst_clapper_subtitle_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info) +{ + GstClapperSubtitleInfo *info = (GstClapperSubtitleInfo *) stream_info; + + if (stream_info->tags) { + + /* free the old language info */ + g_free (info->language); + info->language = NULL; + + /* First try to get the language full name from tag, if name is not + * available then try language code. If we find the language code + * then use gstreamer api to translate code to full name. + */ + gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME, + &info->language); + if (!info->language) { + gchar *lang_code = NULL; + + gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE, + &lang_code); + if (lang_code) { + info->language = g_strdup (gst_tag_get_language_name (lang_code)); + g_free (lang_code); + } + } + + /* If we are still failed to find language name then check if external + * subtitle is loaded and compare the stream index between current sub + * stream index with our stream index and if matches then declare it as + * external subtitle and use the filename. + */ + if (!info->language) { + gint text_index = -1; + gchar *suburi = NULL; + + g_object_get (G_OBJECT (self->playbin), "current-suburi", &suburi, NULL); + if (suburi) { + if (self->use_playbin3) { + if (g_str_equal (self->subtitle_sid, stream_info->stream_id)) + info->language = g_path_get_basename (suburi); + } else { + g_object_get (G_OBJECT (self->playbin), "current-text", &text_index, + NULL); + if (text_index == gst_clapper_stream_info_get_index (stream_info)) + info->language = g_path_get_basename (suburi); + } + g_free (suburi); + } + } + + } else { + g_free (info->language); + info->language = NULL; + } + + GST_DEBUG_OBJECT (self, "language=%s", info->language); +} + +static void +gst_clapper_video_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info) +{ + GstClapperVideoInfo *info = (GstClapperVideoInfo *) stream_info; + + if (stream_info->caps) { + GstStructure *s; + + s = gst_caps_get_structure (stream_info->caps, 0); + if (s) { + gint width, height; + gint fps_n, fps_d; + gint par_n, par_d; + + if (gst_structure_get_int (s, "width", &width)) + info->width = width; + else + info->width = -1; + + if (gst_structure_get_int (s, "height", &height)) + info->height = height; + else + info->height = -1; + + if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) { + info->framerate_num = fps_n; + info->framerate_denom = fps_d; + } else { + info->framerate_num = 0; + info->framerate_denom = 1; + } + + + if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) { + info->par_num = par_n; + info->par_denom = par_d; + } else { + info->par_num = 1; + info->par_denom = 1; + } + } + } else { + info->width = info->height = -1; + info->par_num = info->par_denom = 1; + info->framerate_num = 0; + info->framerate_denom = 1; + } + + if (stream_info->tags) { + guint bitrate, max_bitrate; + + if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate)) + info->bitrate = bitrate; + else + info->bitrate = -1; + + if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE, + &max_bitrate) || gst_tag_list_get_uint (stream_info->tags, + GST_TAG_NOMINAL_BITRATE, &max_bitrate)) + info->max_bitrate = max_bitrate; + else + info->max_bitrate = -1; + } else { + info->bitrate = info->max_bitrate = -1; + } + + GST_DEBUG_OBJECT (self, "width=%d height=%d fps=%.2f par=%d:%d " + "bitrate=%d max_bitrate=%d", info->width, info->height, + (gdouble) info->framerate_num / info->framerate_denom, + info->par_num, info->par_denom, info->bitrate, info->max_bitrate); +} + +static void +gst_clapper_audio_info_update (GstClapper * self, + GstClapperStreamInfo * stream_info) +{ + GstClapperAudioInfo *info = (GstClapperAudioInfo *) stream_info; + + if (stream_info->caps) { + GstStructure *s; + + s = gst_caps_get_structure (stream_info->caps, 0); + if (s) { + gint rate, channels; + + if (gst_structure_get_int (s, "rate", &rate)) + info->sample_rate = rate; + else + info->sample_rate = -1; + + if (gst_structure_get_int (s, "channels", &channels)) + info->channels = channels; + else + info->channels = 0; + } + } else { + info->sample_rate = -1; + info->channels = 0; + } + + if (stream_info->tags) { + guint bitrate, max_bitrate; + + if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate)) + info->bitrate = bitrate; + else + info->bitrate = -1; + + if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE, + &max_bitrate) || gst_tag_list_get_uint (stream_info->tags, + GST_TAG_NOMINAL_BITRATE, &max_bitrate)) + info->max_bitrate = max_bitrate; + else + info->max_bitrate = -1; + + /* if we have old language the free it */ + g_free (info->language); + info->language = NULL; + + /* First try to get the language full name from tag, if name is not + * available then try language code. If we find the language code + * then use gstreamer api to translate code to full name. + */ + gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME, + &info->language); + if (!info->language) { + gchar *lang_code = NULL; + + gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE, + &lang_code); + if (lang_code) { + info->language = g_strdup (gst_tag_get_language_name (lang_code)); + g_free (lang_code); + } + } + } else { + g_free (info->language); + info->language = NULL; + info->max_bitrate = info->bitrate = -1; + } + + GST_DEBUG_OBJECT (self, "language=%s rate=%d channels=%d bitrate=%d " + "max_bitrate=%d", info->language, info->sample_rate, info->channels, + info->bitrate, info->max_bitrate); +} + +static GstClapperStreamInfo * +gst_clapper_stream_info_find (GstClapperMediaInfo * media_info, + GType type, gint stream_index) +{ + GList *list, *l; + GstClapperStreamInfo *info = NULL; + + if (!media_info) + return NULL; + + list = gst_clapper_media_info_get_stream_list (media_info); + for (l = list; l != NULL; l = l->next) { + info = (GstClapperStreamInfo *) l->data; + if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) { + return info; + } + } + + return NULL; +} + +static GstClapperStreamInfo * +gst_clapper_stream_info_find_from_stream_id (GstClapperMediaInfo * media_info, + const gchar * stream_id) +{ + GList *list, *l; + GstClapperStreamInfo *info = NULL; + + if (!media_info) + return NULL; + + list = gst_clapper_media_info_get_stream_list (media_info); + for (l = list; l != NULL; l = l->next) { + info = (GstClapperStreamInfo *) l->data; + if (g_str_equal (info->stream_id, stream_id)) { + return info; + } + } + + return NULL; +} + +static gboolean +is_track_enabled (GstClapper * self, gint pos) +{ + gint flags; + + g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL); + + if ((flags & pos)) + return TRUE; + + return FALSE; +} + +static GstClapperStreamInfo * +gst_clapper_stream_info_get_current (GstClapper * self, const gchar * prop, + GType type) +{ + gint current; + GstClapperStreamInfo *info; + + if (!self->media_info) + return NULL; + + g_object_get (G_OBJECT (self->playbin), prop, ¤t, NULL); + g_mutex_lock (&self->lock); + info = gst_clapper_stream_info_find (self->media_info, type, current); + if (info) + info = gst_clapper_stream_info_copy (info); + g_mutex_unlock (&self->lock); + + return info; +} + +static GstClapperStreamInfo * +gst_clapper_stream_info_get_current_from_stream_id (GstClapper * self, + const gchar * stream_id, GType type) +{ + GstClapperStreamInfo *info; + + if (!self->media_info || !stream_id) + return NULL; + + g_mutex_lock (&self->lock); + info = + gst_clapper_stream_info_find_from_stream_id (self->media_info, stream_id); + if (info && G_OBJECT_TYPE (info) == type) + info = gst_clapper_stream_info_copy (info); + else + info = NULL; + g_mutex_unlock (&self->lock); + + return info; +} + +static void +stream_notify_cb (GstStreamCollection * collection, GstStream * stream, + GParamSpec * pspec, GstClapper * self) +{ + GstClapperStreamInfo *info; + const gchar *stream_id; + gboolean emit_signal = FALSE; + + if (!self->media_info) + return; + + if (G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_CAPS && + G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_TAG_LIST) + return; + + stream_id = gst_stream_get_stream_id (stream); + g_mutex_lock (&self->lock); + info = + gst_clapper_stream_info_find_from_stream_id (self->media_info, stream_id); + if (info) { + gst_clapper_stream_info_update_from_stream (self, info, stream); + emit_signal = TRUE; + } + g_mutex_unlock (&self->lock); + + if (emit_signal) + emit_media_info_updated_signal (self); +} + +static void +gst_clapper_stream_info_update (GstClapper * self, GstClapperStreamInfo * s) +{ + if (GST_IS_CLAPPER_VIDEO_INFO (s)) + gst_clapper_video_info_update (self, s); + else if (GST_IS_CLAPPER_AUDIO_INFO (s)) + gst_clapper_audio_info_update (self, s); + else + gst_clapper_subtitle_info_update (self, s); +} + +static gchar * +stream_info_get_codec (GstClapperStreamInfo * s) +{ + const gchar *type; + GstTagList *tags; + gchar *codec = NULL; + + if (GST_IS_CLAPPER_VIDEO_INFO (s)) + type = GST_TAG_VIDEO_CODEC; + else if (GST_IS_CLAPPER_AUDIO_INFO (s)) + type = GST_TAG_AUDIO_CODEC; + else + type = GST_TAG_SUBTITLE_CODEC; + + tags = gst_clapper_stream_info_get_tags (s); + if (tags) { + gst_tag_list_get_string (tags, type, &codec); + if (!codec) + gst_tag_list_get_string (tags, GST_TAG_CODEC, &codec); + } + + if (!codec) { + GstCaps *caps; + caps = gst_clapper_stream_info_get_caps (s); + if (caps) { + codec = gst_pb_utils_get_codec_description (caps); + } + } + + return codec; +} + +static void +gst_clapper_stream_info_update_tags_and_caps (GstClapper * self, + GstClapperStreamInfo * s) +{ + GstTagList *tags; + gint stream_index; + + stream_index = gst_clapper_stream_info_get_index (s); + + if (GST_IS_CLAPPER_VIDEO_INFO (s)) + g_signal_emit_by_name (self->playbin, "get-video-tags", + stream_index, &tags); + else if (GST_IS_CLAPPER_AUDIO_INFO (s)) + g_signal_emit_by_name (self->playbin, "get-audio-tags", + stream_index, &tags); + else + g_signal_emit_by_name (self->playbin, "get-text-tags", stream_index, &tags); + + if (s->tags) + gst_tag_list_unref (s->tags); + s->tags = tags; + + if (s->caps) + gst_caps_unref (s->caps); + s->caps = get_caps (self, stream_index, G_OBJECT_TYPE (s)); + + g_free (s->codec); + s->codec = stream_info_get_codec (s); + + GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p", + gst_clapper_stream_info_get_stream_type (s), stream_index, + s->tags, s->caps); + + gst_clapper_stream_info_update (self, s); +} + +static void +gst_clapper_streams_info_create (GstClapper * self, + GstClapperMediaInfo * media_info, const gchar * prop, GType type) +{ + gint i; + gint total = -1; + GstClapperStreamInfo *s; + + if (!media_info) + return; + + g_object_get (G_OBJECT (self->playbin), prop, &total, NULL); + + GST_DEBUG_OBJECT (self, "%s: %d", prop, total); + + for (i = 0; i < total; i++) { + /* check if stream already exist in the list */ + s = gst_clapper_stream_info_find (media_info, type, i); + + if (!s) { + /* create a new stream info instance */ + s = gst_clapper_stream_info_new (i, type); + + /* add the object in stream list */ + media_info->stream_list = g_list_append (media_info->stream_list, s); + + /* based on type, add the object in its corresponding stream_ list */ + if (GST_IS_CLAPPER_AUDIO_INFO (s)) + media_info->audio_stream_list = g_list_append + (media_info->audio_stream_list, s); + else if (GST_IS_CLAPPER_VIDEO_INFO (s)) + media_info->video_stream_list = g_list_append + (media_info->video_stream_list, s); + else + media_info->subtitle_stream_list = g_list_append + (media_info->subtitle_stream_list, s); + + GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d", + gst_clapper_stream_info_get_stream_type (s), i); + } + + gst_clapper_stream_info_update_tags_and_caps (self, s); + } +} + +static void +gst_clapper_stream_info_update_from_stream (GstClapper * self, + GstClapperStreamInfo * s, GstStream * stream) +{ + if (s->tags) + gst_tag_list_unref (s->tags); + s->tags = gst_stream_get_tags (stream); + + if (s->caps) + gst_caps_unref (s->caps); + s->caps = gst_stream_get_caps (stream); + + g_free (s->codec); + s->codec = stream_info_get_codec (s); + + GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p", + gst_clapper_stream_info_get_stream_type (s), s->stream_index, + s->tags, s->caps); + + gst_clapper_stream_info_update (self, s); +} + +static void +gst_clapper_streams_info_create_from_collection (GstClapper * self, + GstClapperMediaInfo * media_info, GstStreamCollection * collection) +{ + guint i; + guint total; + GstClapperStreamInfo *s; + guint n_audio = 0; + guint n_video = 0; + guint n_text = 0; + + if (!media_info || !collection) + return; + + total = gst_stream_collection_get_size (collection); + + for (i = 0; i < total; i++) { + GstStream *stream = gst_stream_collection_get_stream (collection, i); + GstStreamType stream_type = gst_stream_get_stream_type (stream); + const gchar *stream_id = gst_stream_get_stream_id (stream); + + if (stream_type & GST_STREAM_TYPE_AUDIO) { + s = gst_clapper_stream_info_new (n_audio, GST_TYPE_CLAPPER_AUDIO_INFO); + n_audio++; + } else if (stream_type & GST_STREAM_TYPE_VIDEO) { + s = gst_clapper_stream_info_new (n_video, GST_TYPE_CLAPPER_VIDEO_INFO); + n_video++; + } else if (stream_type & GST_STREAM_TYPE_TEXT) { + s = gst_clapper_stream_info_new (n_text, GST_TYPE_CLAPPER_SUBTITLE_INFO); + n_text++; + } else { + GST_DEBUG_OBJECT (self, "Unknown type stream %d", i); + continue; + } + + s->stream_id = g_strdup (stream_id); + + /* add the object in stream list */ + media_info->stream_list = g_list_append (media_info->stream_list, s); + + /* based on type, add the object in its corresponding stream_ list */ + if (GST_IS_CLAPPER_AUDIO_INFO (s)) + media_info->audio_stream_list = g_list_append + (media_info->audio_stream_list, s); + else if (GST_IS_CLAPPER_VIDEO_INFO (s)) + media_info->video_stream_list = g_list_append + (media_info->video_stream_list, s); + else + media_info->subtitle_stream_list = g_list_append + (media_info->subtitle_stream_list, s); + + GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d", + gst_clapper_stream_info_get_stream_type (s), s->stream_index); + + gst_clapper_stream_info_update_from_stream (self, s, stream); + } +} + +static void +video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + g_mutex_lock (&self->lock); + gst_clapper_streams_info_create (self, self->media_info, + "n-video", GST_TYPE_CLAPPER_VIDEO_INFO); + g_mutex_unlock (&self->lock); +} + +static void +audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + g_mutex_lock (&self->lock); + gst_clapper_streams_info_create (self, self->media_info, + "n-audio", GST_TYPE_CLAPPER_AUDIO_INFO); + g_mutex_unlock (&self->lock); +} + +static void +subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + g_mutex_lock (&self->lock); + gst_clapper_streams_info_create (self, self->media_info, + "n-text", GST_TYPE_CLAPPER_SUBTITLE_INFO); + g_mutex_unlock (&self->lock); +} + +static void * +get_title (GstTagList * tags) +{ + gchar *title = NULL; + + gst_tag_list_get_string (tags, GST_TAG_TITLE, &title); + if (!title) + gst_tag_list_get_string (tags, GST_TAG_TITLE_SORTNAME, &title); + + return title; +} + +static void * +get_container_format (GstTagList * tags) +{ + gchar *container = NULL; + + gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container); + + /* TODO: If container is not available then maybe consider + * parsing caps or file extension to guess the container format. + */ + + return container; +} + +static void * +get_from_tags (GstClapper * self, GstClapperMediaInfo * media_info, + void *(*func) (GstTagList *)) +{ + GList *l; + void *ret = NULL; + + if (media_info->tags) { + ret = func (media_info->tags); + if (ret) + return ret; + } + + /* if global tag does not exit then try video and audio streams */ + GST_DEBUG_OBJECT (self, "trying video tags"); + for (l = gst_clapper_media_info_get_video_streams (media_info); l != NULL; + l = l->next) { + GstTagList *tags; + + tags = gst_clapper_stream_info_get_tags ((GstClapperStreamInfo *) l->data); + if (tags) + ret = func (tags); + + if (ret) + return ret; + } + + GST_DEBUG_OBJECT (self, "trying audio tags"); + for (l = gst_clapper_media_info_get_audio_streams (media_info); l != NULL; + l = l->next) { + GstTagList *tags; + + tags = gst_clapper_stream_info_get_tags ((GstClapperStreamInfo *) l->data); + if (tags) + ret = func (tags); + + if (ret) + return ret; + } + + GST_DEBUG_OBJECT (self, "failed to get the information from tags"); + return NULL; +} + +static void * +get_cover_sample (GstTagList * tags) +{ + GstSample *cover_sample = NULL; + + gst_tag_list_get_sample (tags, GST_TAG_IMAGE, &cover_sample); + if (!cover_sample) + gst_tag_list_get_sample (tags, GST_TAG_PREVIEW_IMAGE, &cover_sample); + + return cover_sample; +} + +static GstClapperMediaInfo * +gst_clapper_media_info_create (GstClapper * self) +{ + GstClapperMediaInfo *media_info; + GstQuery *query; + + GST_DEBUG_OBJECT (self, "begin"); + media_info = gst_clapper_media_info_new (self->uri); + media_info->duration = gst_clapper_get_duration (self); + media_info->tags = self->global_tags; + media_info->is_live = self->is_live; + self->global_tags = NULL; + + query = gst_query_new_seeking (GST_FORMAT_TIME); + if (gst_element_query (self->playbin, query)) + gst_query_parse_seeking (query, NULL, &media_info->seekable, NULL, NULL); + gst_query_unref (query); + + if (self->use_playbin3 && self->collection) { + gst_clapper_streams_info_create_from_collection (self, media_info, + self->collection); + } else { + /* create audio/video/sub streams */ + gst_clapper_streams_info_create (self, media_info, "n-video", + GST_TYPE_CLAPPER_VIDEO_INFO); + gst_clapper_streams_info_create (self, media_info, "n-audio", + GST_TYPE_CLAPPER_AUDIO_INFO); + gst_clapper_streams_info_create (self, media_info, "n-text", + GST_TYPE_CLAPPER_SUBTITLE_INFO); + } + + media_info->title = get_from_tags (self, media_info, get_title); + media_info->container = + get_from_tags (self, media_info, get_container_format); + media_info->image_sample = get_from_tags (self, media_info, get_cover_sample); + + GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT + " seekable: %s live: %s container: %s image_sample %p", + media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration), + media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no", + media_info->container, media_info->image_sample); + + GST_DEBUG_OBJECT (self, "end"); + return media_info; +} + +static void +tags_changed_cb (GstClapper * self, gint stream_index, GType type) +{ + GstClapperStreamInfo *s; + + if (!self->media_info) + return; + + /* update the stream information */ + g_mutex_lock (&self->lock); + s = gst_clapper_stream_info_find (self->media_info, type, stream_index); + gst_clapper_stream_info_update_tags_and_caps (self, s); + g_mutex_unlock (&self->lock); + + emit_media_info_updated_signal (self); +} + +static void +video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_CLAPPER (user_data), stream_index, + GST_TYPE_CLAPPER_VIDEO_INFO); +} + +static void +audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_CLAPPER (user_data), stream_index, + GST_TYPE_CLAPPER_AUDIO_INFO); +} + +static void +subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, + gpointer user_data) +{ + tags_changed_cb (GST_CLAPPER (user_data), stream_index, + GST_TYPE_CLAPPER_SUBTITLE_INFO); +} + +static void +volume_changed_dispatch (gpointer user_data) +{ + GstClapper *clapper = user_data; + + if (clapper->inhibit_sigs) + return; + + g_signal_emit (clapper, signals[SIGNAL_VOLUME_CHANGED], 0); + g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_VOLUME]); +} + +static void +volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, + GstClapper * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_VOLUME_CHANGED], 0, NULL, NULL, NULL) != 0) { + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + volume_changed_dispatch, g_object_ref (self), + (GDestroyNotify) g_object_unref); + } +} + +static void +mute_changed_dispatch (gpointer user_data) +{ + GstClapper *clapper = user_data; + + if (clapper->inhibit_sigs) + return; + + g_signal_emit (clapper, signals[SIGNAL_MUTE_CHANGED], 0); + g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_MUTE]); +} + +static void +mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, + GstClapper * self) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_MUTE_CHANGED], 0, NULL, NULL, NULL) != 0) { + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + mute_changed_dispatch, g_object_ref (self), + (GDestroyNotify) g_object_unref); + } +} + +static void +source_setup_cb (GstElement * playbin, GstElement * source, GstClapper * self) +{ + gchar *user_agent; + + user_agent = gst_clapper_config_get_user_agent (self->config); + if (user_agent) { + GParamSpec *prop; + + prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source), + "user-agent"); + if (prop && prop->value_type == G_TYPE_STRING) { + GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent); + g_object_set (source, "user-agent", user_agent, NULL); + } + + g_free (user_agent); + } +} + +static gpointer +gst_clapper_main (gpointer data) +{ + GstClapper *self = GST_CLAPPER (data); + GstBus *bus; + GSource *source; + GstElement *scaletempo; + const gchar *env; + + GST_TRACE_OBJECT (self, "Starting main thread"); + + g_main_context_push_thread_default (self->context); + + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self, + NULL); + g_source_attach (source, self->context); + g_source_unref (source); + + env = g_getenv ("GST_CLAPPER_USE_PLAYBIN3"); + if (env && g_str_has_prefix (env, "1")) + self->use_playbin3 = TRUE; + + if (self->use_playbin3) { + GST_DEBUG_OBJECT (self, "playbin3 enabled"); + self->playbin = gst_element_factory_make ("playbin3", "playbin3"); + } else { + self->playbin = gst_element_factory_make ("playbin", "playbin"); + } + + if (!self->playbin) { + g_error ("GstClapper: 'playbin' element not found, please check your setup"); + g_assert_not_reached (); + } + + gst_object_ref_sink (self->playbin); + + if (self->video_renderer) { + GstElement *video_sink = + gst_clapper_video_renderer_create_video_sink (self->video_renderer, + self); + + if (video_sink) + g_object_set (self->playbin, "video-sink", video_sink, NULL); + } + + scaletempo = gst_element_factory_make ("scaletempo", NULL); + if (scaletempo) { + g_object_set (self->playbin, "audio-filter", scaletempo, NULL); + } else { + g_warning ("GstClapper: scaletempo element not available. Audio pitch " + "will not be preserved during trick modes"); + } + + self->bus = bus = gst_element_get_bus (self->playbin); + gst_bus_add_signal_watch (bus); + + g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + G_CALLBACK (state_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::buffering", + G_CALLBACK (buffering_cb), self); + g_signal_connect (G_OBJECT (bus), "message::clock-lost", + G_CALLBACK (clock_lost_cb), self); + g_signal_connect (G_OBJECT (bus), "message::duration-changed", + G_CALLBACK (duration_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::latency", + G_CALLBACK (latency_cb), self); + g_signal_connect (G_OBJECT (bus), "message::request-state", + G_CALLBACK (request_state_cb), self); + g_signal_connect (G_OBJECT (bus), "message::element", + G_CALLBACK (element_cb), self); + g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self); + + if (self->use_playbin3) { + g_signal_connect (G_OBJECT (bus), "message::stream-collection", + G_CALLBACK (stream_collection_cb), self); + g_signal_connect (G_OBJECT (bus), "message::streams-selected", + G_CALLBACK (streams_selected_cb), self); + } else { + g_signal_connect (self->playbin, "video-changed", + G_CALLBACK (video_changed_cb), self); + g_signal_connect (self->playbin, "audio-changed", + G_CALLBACK (audio_changed_cb), self); + g_signal_connect (self->playbin, "text-changed", + G_CALLBACK (subtitle_changed_cb), self); + + g_signal_connect (self->playbin, "video-tags-changed", + G_CALLBACK (video_tags_changed_cb), self); + g_signal_connect (self->playbin, "audio-tags-changed", + G_CALLBACK (audio_tags_changed_cb), self); + g_signal_connect (self->playbin, "text-tags-changed", + G_CALLBACK (subtitle_tags_changed_cb), self); + } + + g_signal_connect (self->playbin, "notify::volume", + G_CALLBACK (volume_notify_cb), self); + g_signal_connect (self->playbin, "notify::mute", + G_CALLBACK (mute_notify_cb), self); + g_signal_connect (self->playbin, "source-setup", + G_CALLBACK (source_setup_cb), self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + change_state (self, GST_CLAPPER_STATE_STOPPED); + self->buffering = 100; + self->is_eos = FALSE; + self->is_live = FALSE; + self->rate = 1.0; + + GST_TRACE_OBJECT (self, "Starting main loop"); + g_main_loop_run (self->loop); + GST_TRACE_OBJECT (self, "Stopped main loop"); + + gst_bus_remove_signal_watch (bus); + gst_object_unref (bus); + + remove_tick_source (self); + remove_ready_timeout_source (self); + + g_mutex_lock (&self->lock); + if (self->media_info) { + g_object_unref (self->media_info); + self->media_info = NULL; + } + + remove_seek_source (self); + g_mutex_unlock (&self->lock); + + g_main_context_pop_thread_default (self->context); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + if (self->playbin) { + gst_element_set_state (self->playbin, GST_STATE_NULL); + gst_object_unref (self->playbin); + self->playbin = NULL; + } + + GST_TRACE_OBJECT (self, "Stopped main thread"); + + return NULL; +} + +static gpointer +gst_clapper_init_once (G_GNUC_UNUSED gpointer user_data) +{ + gst_init (NULL, NULL); + + GST_DEBUG_CATEGORY_INIT (gst_clapper_debug, "gst-clapper", 0, "GstClapper"); + gst_clapper_error_quark (); + + return NULL; +} + +/** + * gst_clapper_new: + * @video_renderer: (transfer full) (allow-none): GstClapperVideoRenderer to use + * @signal_dispatcher: (transfer full) (allow-none): GstClapperSignalDispatcher to use + * + * Creates a new #GstClapper instance that uses @signal_dispatcher to dispatch + * signals to some event loop system, or emits signals directly if NULL is + * passed. See gst_clapper_g_main_context_signal_dispatcher_new(). + * + * Video is going to be rendered by @video_renderer, or if %NULL is provided + * no special video set up will be done and some default handling will be + * performed. + * + * Returns: (transfer full): a new #GstClapper instance + */ +GstClapper * +gst_clapper_new (GstClapperVideoRenderer * video_renderer, + GstClapperSignalDispatcher * signal_dispatcher) +{ + static GOnce once = G_ONCE_INIT; + GstClapper *self; + + g_once (&once, gst_clapper_init_once, NULL); + + self = + g_object_new (GST_TYPE_CLAPPER, "video-renderer", video_renderer, + "signal-dispatcher", signal_dispatcher, NULL); + gst_object_ref_sink (self); + + if (video_renderer) + g_object_unref (video_renderer); + if (signal_dispatcher) + g_object_unref (signal_dispatcher); + + return self; +} + +static gboolean +gst_clapper_play_internal (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Play"); + + g_mutex_lock (&self->lock); + if (!self->uri) { + g_mutex_unlock (&self->lock); + return G_SOURCE_REMOVE; + } + g_mutex_unlock (&self->lock); + + remove_ready_timeout_source (self); + self->target_state = GST_STATE_PLAYING; + + if (self->current_state < GST_STATE_PAUSED) + change_state (self, GST_CLAPPER_STATE_BUFFERING); + + if (self->current_state >= GST_STATE_PAUSED && !self->is_eos + && self->buffering >= 100 && !(self->seek_position != GST_CLOCK_TIME_NONE + || self->seek_pending)) { + state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING); + } else { + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + } + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to play")); + return G_SOURCE_REMOVE; + } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) { + self->is_live = TRUE; + GST_DEBUG_OBJECT (self, "Pipeline is live"); + } + + if (self->is_eos) { + gboolean ret; + + GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning"); + self->is_eos = FALSE; + ret = + gst_element_seek_simple (self->playbin, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 0); + if (!ret) { + GST_ERROR_OBJECT (self, "Seek to beginning failed"); + gst_clapper_stop_internal (self, TRUE); + gst_clapper_play_internal (self); + } + } + + return G_SOURCE_REMOVE; +} + +/** + * gst_clapper_play: + * @clapper: #GstClapper instance + * + * Request to play the loaded stream. + */ +void +gst_clapper_play (GstClapper * self) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_mutex_lock (&self->lock); + self->inhibit_sigs = FALSE; + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_clapper_play_internal, self, NULL); +} + +static gboolean +gst_clapper_pause_internal (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Pause"); + + g_mutex_lock (&self->lock); + if (!self->uri) { + g_mutex_unlock (&self->lock); + return G_SOURCE_REMOVE; + } + g_mutex_unlock (&self->lock); + + tick_cb (self); + remove_tick_source (self); + remove_ready_timeout_source (self); + + self->target_state = GST_STATE_PAUSED; + + if (self->current_state < GST_STATE_PAUSED) + change_state (self, GST_CLAPPER_STATE_BUFFERING); + + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to pause")); + return G_SOURCE_REMOVE; + } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) { + self->is_live = TRUE; + GST_DEBUG_OBJECT (self, "Pipeline is live"); + } + + if (self->is_eos) { + gboolean ret; + + GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning"); + self->is_eos = FALSE; + ret = + gst_element_seek_simple (self->playbin, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 0); + if (!ret) { + GST_ERROR_OBJECT (self, "Seek to beginning failed"); + gst_clapper_stop_internal (self, TRUE); + gst_clapper_pause_internal (self); + } + } + + return G_SOURCE_REMOVE; +} + +/** + * gst_clapper_pause: + * @clapper: #GstClapper instance + * + * Pauses the current stream. + */ +void +gst_clapper_pause (GstClapper * self) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_mutex_lock (&self->lock); + self->inhibit_sigs = FALSE; + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_clapper_pause_internal, self, NULL); +} + +static void +gst_clapper_stop_internal (GstClapper * self, gboolean transient) +{ + /* directly return if we're already stopped */ + if (self->current_state <= GST_STATE_READY && + self->target_state <= GST_STATE_READY) + return; + + GST_DEBUG_OBJECT (self, "Stop (transient %d)", transient); + + tick_cb (self); + remove_tick_source (self); + + add_ready_timeout_source (self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_READY; + self->is_live = FALSE; + self->is_eos = FALSE; + gst_bus_set_flushing (self->bus, TRUE); + gst_element_set_state (self->playbin, GST_STATE_READY); + gst_bus_set_flushing (self->bus, FALSE); + change_state (self, transient + && self->app_state != + GST_CLAPPER_STATE_STOPPED ? GST_CLAPPER_STATE_BUFFERING : + GST_CLAPPER_STATE_STOPPED); + self->buffering = 100; + self->cached_duration = GST_CLOCK_TIME_NONE; + g_mutex_lock (&self->lock); + if (self->media_info) { + g_object_unref (self->media_info); + self->media_info = NULL; + } + if (self->global_tags) { + gst_tag_list_unref (self->global_tags); + self->global_tags = NULL; + } + self->seek_pending = FALSE; + remove_seek_source (self); + self->seek_position = GST_CLOCK_TIME_NONE; + self->last_seek_time = GST_CLOCK_TIME_NONE; + self->rate = 1.0; + if (self->collection) { + if (self->stream_notify_id) + g_signal_handler_disconnect (self->collection, self->stream_notify_id); + self->stream_notify_id = 0; + gst_object_unref (self->collection); + self->collection = NULL; + } + g_free (self->video_sid); + g_free (self->audio_sid); + g_free (self->subtitle_sid); + self->video_sid = NULL; + self->audio_sid = NULL; + self->subtitle_sid = NULL; + g_mutex_unlock (&self->lock); +} + +static gboolean +gst_clapper_stop_internal_dispatch (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + gst_clapper_stop_internal (self, FALSE); + + return G_SOURCE_REMOVE; +} + + +/** + * gst_clapper_stop: + * @clapper: #GstClapper instance + * + * Stops playing the current stream and resets to the first position + * in the stream. + */ +void +gst_clapper_stop (GstClapper * self) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_mutex_lock (&self->lock); + self->inhibit_sigs = TRUE; + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, + gst_clapper_stop_internal_dispatch, self, NULL); +} + +/* Must be called with lock from main context, releases lock! */ +static void +gst_clapper_seek_internal_locked (GstClapper * self) +{ + gboolean ret; + GstClockTime position; + gdouble rate; + GstStateChangeReturn state_ret; + GstEvent *s_event; + GstSeekFlags flags = 0; + gboolean accurate = FALSE; + + remove_seek_source (self); + + /* Only seek in PAUSED */ + if (self->current_state < GST_STATE_PAUSED) { + return; + } else if (self->current_state != GST_STATE_PAUSED) { + g_mutex_unlock (&self->lock); + state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to seek")); + g_mutex_lock (&self->lock); + return; + } + g_mutex_lock (&self->lock); + return; + } + + self->last_seek_time = gst_util_get_timestamp (); + position = self->seek_position; + self->seek_position = GST_CLOCK_TIME_NONE; + self->seek_pending = TRUE; + rate = self->rate; + g_mutex_unlock (&self->lock); + + remove_tick_source (self); + self->is_eos = FALSE; + + flags |= GST_SEEK_FLAG_FLUSH; + + accurate = gst_clapper_config_get_seek_accurate (self->config); + + if (accurate) { + flags |= GST_SEEK_FLAG_ACCURATE; + } else { + flags &= ~GST_SEEK_FLAG_ACCURATE; + } + + if (rate != 1.0) { + flags |= GST_SEEK_FLAG_TRICKMODE; + } + + if (rate >= 0.0) { + s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); + } else { + s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, + GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position); + } + + GST_DEBUG_OBJECT (self, "Seek with rate %.2lf to %" GST_TIME_FORMAT, + rate, GST_TIME_ARGS (position)); + + ret = gst_element_send_event (self->playbin, s_event); + if (!ret) + emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, + "Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position))); + + g_mutex_lock (&self->lock); +} + +static gboolean +gst_clapper_seek_internal (gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + g_mutex_lock (&self->lock); + gst_clapper_seek_internal_locked (self); + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +/** + * gst_clapper_set_rate: + * @clapper: #GstClapper instance + * @rate: playback rate + * + * Playback at specified rate + */ +void +gst_clapper_set_rate (GstClapper * self, gdouble rate) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + g_return_if_fail (rate != 0.0); + + g_object_set (self, "rate", rate, NULL); +} + +/** + * gst_clapper_get_rate: + * @clapper: #GstClapper instance + * + * Returns: current playback rate + */ +gdouble +gst_clapper_get_rate (GstClapper * self) +{ + gdouble val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_RATE); + + g_object_get (self, "rate", &val, NULL); + + return val; +} + +/** + * gst_clapper_seek: + * @clapper: #GstClapper instance + * @position: position to seek in nanoseconds + * + * Seeks the currently-playing stream to the absolute @position time + * in nanoseconds. + */ +void +gst_clapper_seek (GstClapper * self, GstClockTime position) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + g_return_if_fail (GST_CLOCK_TIME_IS_VALID (position)); + + g_mutex_lock (&self->lock); + if (self->media_info && !self->media_info->seekable) { + GST_DEBUG_OBJECT (self, "Media is not seekable"); + g_mutex_unlock (&self->lock); + return; + } + + self->seek_position = position; + + /* If there is no seek being dispatch to the main context currently do that, + * otherwise we just updated the seek position so that it will be taken by + * the seek handler from the main context instead of the old one. + */ + if (!self->seek_source) { + GstClockTime now = gst_util_get_timestamp (); + + /* If no seek is pending or it was started more than 250 mseconds ago seek + * immediately, otherwise wait until the 250 mseconds have passed */ + if (!self->seek_pending || (now - self->last_seek_time > 250 * GST_MSECOND)) { + self->seek_source = g_idle_source_new (); + g_source_set_callback (self->seek_source, + (GSourceFunc) gst_clapper_seek_internal, self, NULL); + GST_TRACE_OBJECT (self, "Dispatching seek to position %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + g_source_attach (self->seek_source, self->context); + } else { + guint delay = 250000 - (now - self->last_seek_time) / 1000; + + /* Note that last_seek_time must be set to something at this point and + * it must be smaller than 250 mseconds */ + self->seek_source = g_timeout_source_new (delay); + g_source_set_callback (self->seek_source, + (GSourceFunc) gst_clapper_seek_internal, self, NULL); + + GST_TRACE_OBJECT (self, + "Delaying seek to position %" GST_TIME_FORMAT " by %u us", + GST_TIME_ARGS (position), delay); + g_source_attach (self->seek_source, self->context); + } + } + g_mutex_unlock (&self->lock); +} + +static void +remove_seek_source (GstClapper * self) +{ + if (!self->seek_source) + return; + + g_source_destroy (self->seek_source); + g_source_unref (self->seek_source); + self->seek_source = NULL; +} + +/** + * gst_clapper_get_uri: + * @clapper: #GstClapper instance + * + * Gets the URI of the currently-playing stream. + * + * Returns: (transfer full): a string containing the URI of the + * currently-playing stream. g_free() after usage. + */ +gchar * +gst_clapper_get_uri (GstClapper * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_URI); + + g_object_get (self, "uri", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_uri: + * @clapper: #GstClapper instance + * @uri: next URI to play. + * + * Sets the next URI to play. + */ +void +gst_clapper_set_uri (GstClapper * self, const gchar * val) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "uri", val, NULL); +} + +/** + * gst_clapper_set_subtitle_uri: + * @clapper: #GstClapper instance + * @uri: subtitle URI + * + * Sets the external subtitle URI. This should be combined with a call to + * gst_clapper_set_subtitle_track_enabled(@clapper, TRUE) so the subtitles are actually + * rendered. + */ +void +gst_clapper_set_subtitle_uri (GstClapper * self, const gchar * suburi) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "suburi", suburi, NULL); +} + +/** + * gst_clapper_get_subtitle_uri: + * @clapper: #GstClapper instance + * + * current subtitle URI + * + * Returns: (transfer full): URI of the current external subtitle. + * g_free() after usage. + */ +gchar * +gst_clapper_get_subtitle_uri (GstClapper * self) +{ + gchar *val = NULL; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + g_object_get (self, "suburi", &val, NULL); + + return val; +} + +/** + * gst_clapper_get_position: + * @clapper: #GstClapper instance + * + * Returns: the absolute position time, in nanoseconds, of the + * currently-playing stream. + */ +GstClockTime +gst_clapper_get_position (GstClapper * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_POSITION); + + g_object_get (self, "position", &val, NULL); + + return val; +} + +/** + * gst_clapper_get_duration: + * @clapper: #GstClapper instance + * + * Retrieves the duration of the media stream that self represents. + * + * Returns: the duration of the currently-playing media stream, in + * nanoseconds. + */ +GstClockTime +gst_clapper_get_duration (GstClapper * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_DURATION); + + g_object_get (self, "duration", &val, NULL); + + return val; +} + +/** + * gst_clapper_get_volume: + * @clapper: #GstClapper instance + * + * Returns the current volume level, as a percentage between 0 and 1. + * + * Returns: the volume as percentage between 0 and 1. + */ +gdouble +gst_clapper_get_volume (GstClapper * self) +{ + gdouble val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_VOLUME); + + g_object_get (self, "volume", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_volume: + * @clapper: #GstClapper instance + * @val: the new volume level, as a percentage between 0 and 1 + * + * Sets the volume level of the stream as a percentage between 0 and 1. + */ +void +gst_clapper_set_volume (GstClapper * self, gdouble val) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "volume", val, NULL); +} + +/** + * gst_clapper_get_mute: + * @clapper: #GstClapper instance + * + * Returns: %TRUE if the currently-playing stream is muted. + */ +gboolean +gst_clapper_get_mute (GstClapper * self) +{ + gboolean val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_MUTE); + + g_object_get (self, "mute", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_mute: + * @clapper: #GstClapper instance + * @val: Mute state the should be set + * + * %TRUE if the currently-playing stream should be muted. + */ +void +gst_clapper_set_mute (GstClapper * self, gboolean val) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "mute", val, NULL); +} + +/** + * gst_clapper_get_pipeline: + * @clapper: #GstClapper instance + * + * Returns: (transfer full): The internal playbin instance. + * + * The caller should free it with g_object_unref() + */ +GstElement * +gst_clapper_get_pipeline (GstClapper * self) +{ + GstElement *val; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + g_object_get (self, "pipeline", &val, NULL); + + return val; +} + +/** + * gst_clapper_get_media_info: + * @clapper: #GstClapper instance + * + * A Function to get the current media info #GstClapperMediaInfo instance. + * + * Returns: (transfer full): media info instance. + * + * The caller should free it with g_object_unref() + */ +GstClapperMediaInfo * +gst_clapper_get_media_info (GstClapper * self) +{ + GstClapperMediaInfo *info; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + if (!self->media_info) + return NULL; + + g_mutex_lock (&self->lock); + info = gst_clapper_media_info_copy (self->media_info); + g_mutex_unlock (&self->lock); + + return info; +} + +/** + * gst_clapper_get_current_audio_track: + * @clapper: #GstClapper instance + * + * A Function to get current audio #GstClapperAudioInfo instance. + * + * Returns: (transfer full): current audio track. + * + * The caller should free it with g_object_unref() + */ +GstClapperAudioInfo * +gst_clapper_get_current_audio_track (GstClapper * self) +{ + GstClapperAudioInfo *info; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO)) + return NULL; + + if (self->use_playbin3) { + info = (GstClapperAudioInfo *) + gst_clapper_stream_info_get_current_from_stream_id (self, + self->audio_sid, GST_TYPE_CLAPPER_AUDIO_INFO); + } else { + info = (GstClapperAudioInfo *) gst_clapper_stream_info_get_current (self, + "current-audio", GST_TYPE_CLAPPER_AUDIO_INFO); + } + + return info; +} + +/** + * gst_clapper_get_current_video_track: + * @clapper: #GstClapper instance + * + * A Function to get current video #GstClapperVideoInfo instance. + * + * Returns: (transfer full): current video track. + * + * The caller should free it with g_object_unref() + */ +GstClapperVideoInfo * +gst_clapper_get_current_video_track (GstClapper * self) +{ + GstClapperVideoInfo *info; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO)) + return NULL; + + if (self->use_playbin3) { + info = (GstClapperVideoInfo *) + gst_clapper_stream_info_get_current_from_stream_id (self, + self->video_sid, GST_TYPE_CLAPPER_VIDEO_INFO); + } else { + info = (GstClapperVideoInfo *) gst_clapper_stream_info_get_current (self, + "current-video", GST_TYPE_CLAPPER_VIDEO_INFO); + } + + return info; +} + +/** + * gst_clapper_get_current_subtitle_track: + * @clapper: #GstClapper instance + * + * A Function to get current subtitle #GstClapperSubtitleInfo instance. + * + * Returns: (transfer full): current subtitle track. + * + * The caller should free it with g_object_unref() + */ +GstClapperSubtitleInfo * +gst_clapper_get_current_subtitle_track (GstClapper * self) +{ + GstClapperSubtitleInfo *info; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE)) + return NULL; + + if (self->use_playbin3) { + info = (GstClapperSubtitleInfo *) + gst_clapper_stream_info_get_current_from_stream_id (self, + self->subtitle_sid, GST_TYPE_CLAPPER_SUBTITLE_INFO); + } else { + info = (GstClapperSubtitleInfo *) gst_clapper_stream_info_get_current (self, + "current-text", GST_TYPE_CLAPPER_SUBTITLE_INFO); + } + + return info; +} + +/* Must be called with lock */ +static gboolean +gst_clapper_select_streams (GstClapper * self) +{ + GList *stream_list = NULL; + gboolean ret = FALSE; + + if (self->audio_sid) + stream_list = g_list_append (stream_list, g_strdup (self->audio_sid)); + if (self->video_sid) + stream_list = g_list_append (stream_list, g_strdup (self->video_sid)); + if (self->subtitle_sid) + stream_list = g_list_append (stream_list, g_strdup (self->subtitle_sid)); + + g_mutex_unlock (&self->lock); + if (stream_list) { + ret = gst_element_send_event (self->playbin, + gst_event_new_select_streams (stream_list)); + g_list_free_full (stream_list, g_free); + } else { + GST_ERROR_OBJECT (self, "No available streams for select-streams"); + } + g_mutex_lock (&self->lock); + + return ret; +} + +/** + * gst_clapper_set_audio_track: + * @clapper: #GstClapper instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the audio track @stream_idex. + */ +gboolean +gst_clapper_set_audio_track (GstClapper * self, gint stream_index) +{ + GstClapperStreamInfo *info; + gboolean ret = TRUE; + + g_return_val_if_fail (GST_IS_CLAPPER (self), 0); + + g_mutex_lock (&self->lock); + info = gst_clapper_stream_info_find (self->media_info, + GST_TYPE_CLAPPER_AUDIO_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index); + return FALSE; + } + + if (self->use_playbin3) { + g_mutex_lock (&self->lock); + g_free (self->audio_sid); + self->audio_sid = g_strdup (info->stream_id); + ret = gst_clapper_select_streams (self); + g_mutex_unlock (&self->lock); + } else { + g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index, + NULL); + } + + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return ret; +} + +/** + * gst_clapper_set_video_track: + * @clapper: #GstClapper instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the video track @stream_index. + */ +gboolean +gst_clapper_set_video_track (GstClapper * self, gint stream_index) +{ + GstClapperStreamInfo *info; + gboolean ret = TRUE; + + g_return_val_if_fail (GST_IS_CLAPPER (self), 0); + + /* check if stream_index exist in our internal media_info list */ + g_mutex_lock (&self->lock); + info = gst_clapper_stream_info_find (self->media_info, + GST_TYPE_CLAPPER_VIDEO_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index); + return FALSE; + } + + if (self->use_playbin3) { + g_mutex_lock (&self->lock); + g_free (self->video_sid); + self->video_sid = g_strdup (info->stream_id); + ret = gst_clapper_select_streams (self); + g_mutex_unlock (&self->lock); + } else { + g_object_set (G_OBJECT (self->playbin), "current-video", stream_index, + NULL); + } + + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return ret; +} + +/** + * gst_clapper_set_subtitle_track: + * @clapper: #GstClapper instance + * @stream_index: stream index + * + * Returns: %TRUE or %FALSE + * + * Sets the subtitle stack @stream_index. + */ +gboolean +gst_clapper_set_subtitle_track (GstClapper * self, gint stream_index) +{ + GstClapperStreamInfo *info; + gboolean ret = TRUE; + + g_return_val_if_fail (GST_IS_CLAPPER (self), 0); + + g_mutex_lock (&self->lock); + info = gst_clapper_stream_info_find (self->media_info, + GST_TYPE_CLAPPER_SUBTITLE_INFO, stream_index); + g_mutex_unlock (&self->lock); + if (!info) { + GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index); + return FALSE; + } + + if (self->use_playbin3) { + g_mutex_lock (&self->lock); + g_free (self->subtitle_sid); + self->subtitle_sid = g_strdup (info->stream_id); + ret = gst_clapper_select_streams (self); + g_mutex_unlock (&self->lock); + } else { + g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL); + } + + GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index); + return ret; +} + +/** + * gst_clapper_set_audio_track_enabled: + * @clapper: #GstClapper instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current audio track. + */ +void +gst_clapper_set_audio_track_enabled (GstClapper * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + if (enabled) + clapper_set_flag (self, GST_PLAY_FLAG_AUDIO); + else + clapper_clear_flag (self, GST_PLAY_FLAG_AUDIO); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_clapper_set_video_track_enabled: + * @clapper: #GstClapper instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current video track. + */ +void +gst_clapper_set_video_track_enabled (GstClapper * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + if (enabled) + clapper_set_flag (self, GST_PLAY_FLAG_VIDEO); + else + clapper_clear_flag (self, GST_PLAY_FLAG_VIDEO); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_clapper_set_subtitle_track_enabled: + * @clapper: #GstClapper instance + * @enabled: TRUE or FALSE + * + * Enable or disable the current subtitle track. + */ +void +gst_clapper_set_subtitle_track_enabled (GstClapper * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + if (enabled) + clapper_set_flag (self, GST_PLAY_FLAG_SUBTITLE); + else + clapper_clear_flag (self, GST_PLAY_FLAG_SUBTITLE); + + GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled"); +} + +/** + * gst_clapper_set_visualization: + * @clapper: #GstClapper instance + * @name: visualization element obtained from + * #gst_clapper_visualizations_get() + * + * Returns: %TRUE if the visualizations was set correctly. Otherwise, + * %FALSE. + */ +gboolean +gst_clapper_set_visualization (GstClapper * self, const gchar * name) +{ + g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE); + + g_mutex_lock (&self->lock); + if (self->current_vis_element) { + gst_object_unref (self->current_vis_element); + self->current_vis_element = NULL; + } + + if (name) { + self->current_vis_element = gst_element_factory_make (name, NULL); + if (!self->current_vis_element) + goto error_no_element; + gst_object_ref_sink (self->current_vis_element); + } + g_object_set (self->playbin, "vis-plugin", self->current_vis_element, NULL); + + g_mutex_unlock (&self->lock); + GST_DEBUG_OBJECT (self, "set vis-plugin to '%s'", name); + + return TRUE; + +error_no_element: + g_mutex_unlock (&self->lock); + GST_WARNING_OBJECT (self, "could not find visualization '%s'", name); + return FALSE; +} + +/** + * gst_clapper_get_current_visualization: + * @clapper: #GstClapper instance + * + * Returns: (transfer full): Name of the currently enabled visualization. + * g_free() after usage. + */ +gchar * +gst_clapper_get_current_visualization (GstClapper * self) +{ + gchar *name = NULL; + GstElement *vis_plugin = NULL; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + if (!is_track_enabled (self, GST_PLAY_FLAG_VIS)) + return NULL; + + g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL); + + if (vis_plugin) { + GstElementFactory *factory = gst_element_get_factory (vis_plugin); + if (factory) + name = g_strdup (gst_plugin_feature_get_name (factory)); + gst_object_unref (vis_plugin); + } + + GST_DEBUG_OBJECT (self, "vis-plugin '%s' %p", name, vis_plugin); + + return name; +} + +/** + * gst_clapper_set_visualization_enabled: + * @clapper: #GstClapper instance + * @enabled: TRUE or FALSE + * + * Enable or disable the visualization. + */ +void +gst_clapper_set_visualization_enabled (GstClapper * self, gboolean enabled) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + if (enabled) + clapper_set_flag (self, GST_PLAY_FLAG_VIS); + else + clapper_clear_flag (self, GST_PLAY_FLAG_VIS); + + GST_DEBUG_OBJECT (self, "visualization is '%s'", + enabled ? "Enabled" : "Disabled"); +} + +struct CBChannelMap +{ + const gchar *label; /* channel label name */ + const gchar *name; /* get_name () */ +}; + +static const struct CBChannelMap cb_channel_map[] = { + /* GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"}, + /* GST_CLAPPER_COLOR_BALANCE_CONTRAST */ {"CONTRAST", "contrast"}, + /* GST_CLAPPER_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"}, + /* GST_CLAPPER_COLOR_BALANCE_HUE */ {"HUE", "hue"}, +}; + +static GstColorBalanceChannel * +gst_clapper_color_balance_find_channel (GstClapper * self, + GstClapperColorBalanceType type) +{ + GstColorBalanceChannel *channel; + const GList *l, *channels; + + if (type < GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS || + type > GST_CLAPPER_COLOR_BALANCE_HUE) + return NULL; + + channels = + gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin)); + for (l = channels; l; l = l->next) { + channel = l->data; + if (g_strrstr (channel->label, cb_channel_map[type].label)) + return channel; + } + + return NULL; +} + +/** + * gst_clapper_has_color_balance: + * @clapper:#GstClapper instance + * + * Checks whether the @clapper has color balance support available. + * + * Returns: %TRUE if @clapper has color balance support. Otherwise, + * %FALSE. + */ +gboolean +gst_clapper_has_color_balance (GstClapper * self) +{ + const GList *channels; + + g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE); + + if (!GST_IS_COLOR_BALANCE (self->playbin)) + return FALSE; + + channels = + gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin)); + return (channels != NULL); +} + +/** + * gst_clapper_set_color_balance: + * @clapper: #GstClapper instance + * @type: #GstClapperColorBalanceType + * @value: The new value for the @type, ranged [0,1] + * + * Sets the current value of the indicated channel @type to the passed + * value. + */ +void +gst_clapper_set_color_balance (GstClapper * self, GstClapperColorBalanceType type, + gdouble value) +{ + GstColorBalanceChannel *channel; + gdouble new_val; + + g_return_if_fail (GST_IS_CLAPPER (self)); + g_return_if_fail (value >= 0.0 && value <= 1.0); + + if (!GST_IS_COLOR_BALANCE (self->playbin)) + return; + + channel = gst_clapper_color_balance_find_channel (self, type); + if (!channel) + return; + + value = CLAMP (value, 0.0, 1.0); + + /* Convert to channel range */ + new_val = channel->min_value + value * ((gdouble) channel->max_value - + (gdouble) channel->min_value); + + gst_color_balance_set_value (GST_COLOR_BALANCE (self->playbin), channel, + new_val); +} + +/** + * gst_clapper_get_color_balance: + * @clapper: #GstClapper instance + * @type: #GstClapperColorBalanceType + * + * Retrieve the current value of the indicated @type. + * + * Returns: The current value of @type, between [0,1]. In case of + * error -1 is returned. + */ +gdouble +gst_clapper_get_color_balance (GstClapper * self, GstClapperColorBalanceType type) +{ + GstColorBalanceChannel *channel; + gint value; + + g_return_val_if_fail (GST_IS_CLAPPER (self), -1); + + if (!GST_IS_COLOR_BALANCE (self->playbin)) + return -1; + + channel = gst_clapper_color_balance_find_channel (self, type); + if (!channel) + return -1; + + value = gst_color_balance_get_value (GST_COLOR_BALANCE (self->playbin), + channel); + + return ((gdouble) value - + (gdouble) channel->min_value) / ((gdouble) channel->max_value - + (gdouble) channel->min_value); +} + +/** + * gst_clapper_get_multiview_mode: + * @clapper: #GstClapper instance + * + * Retrieve the current value of the indicated @type. + * + * Returns: The current value of @type, Default: -1 "none" + */ +GstVideoMultiviewFramePacking +gst_clapper_get_multiview_mode (GstClapper * self) +{ + GstVideoMultiviewFramePacking val = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE; + + g_return_val_if_fail (GST_IS_CLAPPER (self), + GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE); + + g_object_get (self, "video-multiview-mode", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_multiview_mode: + * @clapper: #GstClapper instance + * @mode: The new value for the @type + * + * Sets the current value of the indicated mode @type to the passed + * value. + */ +void +gst_clapper_set_multiview_mode (GstClapper * self, + GstVideoMultiviewFramePacking mode) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "video-multiview-mode", mode, NULL); +} + +/** + * gst_clapper_get_multiview_flags: + * @clapper: #GstClapper instance + * + * Retrieve the current value of the indicated @type. + * + * Returns: The current value of @type, Default: 0x00000000 "none + */ +GstVideoMultiviewFlags +gst_clapper_get_multiview_flags (GstClapper * self) +{ + GstVideoMultiviewFlags val = GST_VIDEO_MULTIVIEW_FLAGS_NONE; + + g_return_val_if_fail (GST_IS_CLAPPER (self), val); + + g_object_get (self, "video-multiview-flags", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_multiview_flags: + * @clapper: #GstClapper instance + * @flags: The new value for the @type + * + * Sets the current value of the indicated mode @type to the passed + * value. + */ +void +gst_clapper_set_multiview_flags (GstClapper * self, GstVideoMultiviewFlags flags) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "video-multiview-flags", flags, NULL); +} + +/** + * gst_clapper_get_audio_video_offset: + * @clapper: #GstClapper instance + * + * Retrieve the current value of audio-video-offset property + * + * Returns: The current value of audio-video-offset in nanoseconds + */ +gint64 +gst_clapper_get_audio_video_offset (GstClapper * self) +{ + gint64 val = 0; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_AUDIO_VIDEO_OFFSET); + + g_object_get (self, "audio-video-offset", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_audio_video_offset: + * @clapper: #GstClapper instance + * @offset: #gint64 in nanoseconds + * + * Sets audio-video-offset property by value of @offset + */ +void +gst_clapper_set_audio_video_offset (GstClapper * self, gint64 offset) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "audio-video-offset", offset, NULL); +} + +/** + * gst_clapper_get_subtitle_video_offset: + * @clapper: #GstClapper instance + * + * Retrieve the current value of subtitle-video-offset property + * + * Returns: The current value of subtitle-video-offset in nanoseconds + */ +gint64 +gst_clapper_get_subtitle_video_offset (GstClapper * self) +{ + gint64 val = 0; + + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_SUBTITLE_VIDEO_OFFSET); + + g_object_get (self, "subtitle-video-offset", &val, NULL); + + return val; +} + +/** + * gst_clapper_set_subtitle_video_offset: + * @clapper: #GstClapper instance + * @offset: #gint64 in nanoseconds + * + * Sets subtitle-video-offset property by value of @offset + */ +void +gst_clapper_set_subtitle_video_offset (GstClapper * self, gint64 offset) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "subtitle-video-offset", offset, NULL); +} + + +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +GType +gst_clapper_color_balance_type_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_COLOR_BALANCE_HUE), "GST_CLAPPER_COLOR_BALANCE_HUE", + "hue"}, + {C_ENUM (GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS), + "GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS", "brightness"}, + {C_ENUM (GST_CLAPPER_COLOR_BALANCE_SATURATION), + "GST_CLAPPER_COLOR_BALANCE_SATURATION", "saturation"}, + {C_ENUM (GST_CLAPPER_COLOR_BALANCE_CONTRAST), + "GST_CLAPPER_COLOR_BALANCE_CONTRAST", "contrast"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperColorBalanceType", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_clapper_color_balance_type_get_name: + * @type: a #GstClapperColorBalanceType + * + * Gets a string representing the given color balance type. + * + * Returns: (transfer none): a string with the name of the color + * balance type. + */ +const gchar * +gst_clapper_color_balance_type_get_name (GstClapperColorBalanceType type) +{ + g_return_val_if_fail (type >= GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS && + type <= GST_CLAPPER_COLOR_BALANCE_HUE, NULL); + + return cb_channel_map[type].name; +} + +GType +gst_clapper_state_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_STATE_STOPPED), "GST_CLAPPER_STATE_STOPPED", "stopped"}, + {C_ENUM (GST_CLAPPER_STATE_BUFFERING), "GST_CLAPPER_STATE_BUFFERING", + "buffering"}, + {C_ENUM (GST_CLAPPER_STATE_PAUSED), "GST_CLAPPER_STATE_PAUSED", "paused"}, + {C_ENUM (GST_CLAPPER_STATE_PLAYING), "GST_CLAPPER_STATE_PLAYING", "playing"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperState", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_clapper_state_get_name: + * @state: a #GstClapperState + * + * Gets a string representing the given state. + * + * Returns: (transfer none): a string with the name of the state. + */ +const gchar * +gst_clapper_state_get_name (GstClapperState state) +{ + switch (state) { + case GST_CLAPPER_STATE_STOPPED: + return "stopped"; + case GST_CLAPPER_STATE_BUFFERING: + return "buffering"; + case GST_CLAPPER_STATE_PAUSED: + return "paused"; + case GST_CLAPPER_STATE_PLAYING: + return "playing"; + } + + g_assert_not_reached (); + return NULL; +} + +GType +gst_clapper_error_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_ERROR_FAILED), "GST_CLAPPER_ERROR_FAILED", "failed"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperError", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_clapper_error_get_name: + * @error: a #GstClapperError + * + * Gets a string representing the given error. + * + * Returns: (transfer none): a string with the given error. + */ +const gchar * +gst_clapper_error_get_name (GstClapperError error) +{ + switch (error) { + case GST_CLAPPER_ERROR_FAILED: + return "failed"; + } + + g_assert_not_reached (); + return NULL; +} + +/** + * gst_clapper_set_config: + * @clapper: #GstClapper instance + * @config: (transfer full): a #GstStructure + * + * Set the configuration of the clapper. If the clapper is already configured, and + * the configuration haven't change, this function will return %TRUE. If the + * clapper is not in the GST_CLAPPER_STATE_STOPPED, this method will return %FALSE + * and active configuration will remain. + * + * @config is a #GstStructure that contains the configuration parameters for + * the clapper. + * + * This function takes ownership of @config. + * + * Returns: %TRUE when the configuration could be set. + */ +gboolean +gst_clapper_set_config (GstClapper * self, GstStructure * config) +{ + g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE); + g_return_val_if_fail (config != NULL, FALSE); + + g_mutex_lock (&self->lock); + + if (self->app_state != GST_CLAPPER_STATE_STOPPED) { + GST_INFO_OBJECT (self, "can't change config while clapper is %s", + gst_clapper_state_get_name (self->app_state)); + g_mutex_unlock (&self->lock); + return FALSE; + } + + if (self->config) + gst_structure_free (self->config); + self->config = config; + g_mutex_unlock (&self->lock); + + return TRUE; +} + +/** + * gst_clapper_get_config: + * @clapper: #GstClapper instance + * + * Get a copy of the current configuration of the clapper. This configuration + * can either be modified and used for the gst_clapper_set_config() call + * or it must be freed after usage. + * + * Returns: (transfer full): a copy of the current configuration of @clapper. Use + * gst_structure_free() after usage or gst_clapper_set_config(). + */ +GstStructure * +gst_clapper_get_config (GstClapper * self) +{ + GstStructure *ret; + + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + g_mutex_lock (&self->lock); + ret = gst_structure_copy (self->config); + g_mutex_unlock (&self->lock); + + return ret; +} + +/** + * gst_clapper_config_set_user_agent: + * @config: a #GstClapper configuration + * @agent: the string to use as user agent + * + * Set the user agent to pass to the server if @clapper needs to connect + * to a server during playback. This is typically used when playing HTTP + * or RTSP streams. + */ +void +gst_clapper_config_set_user_agent (GstStructure * config, const gchar * agent) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (agent != NULL); + + gst_structure_id_set (config, + CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL); +} + +/** + * gst_clapper_config_get_user_agent: + * @config: a #GstClapper configuration + * + * Return the user agent which has been configured using + * gst_clapper_config_set_user_agent() if any. + * + * Returns: (transfer full): the configured agent, or %NULL + */ +gchar * +gst_clapper_config_get_user_agent (const GstStructure * config) +{ + gchar *agent = NULL; + + g_return_val_if_fail (config != NULL, NULL); + + gst_structure_id_get (config, + CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL); + + return agent; +} + +/** + * gst_clapper_config_set_position_update_interval: + * @config: a #GstClapper configuration + * @interval: interval in ms + * + * set interval in milliseconds between two position-updated signals. + * pass 0 to stop updating the position. + */ +void +gst_clapper_config_set_position_update_interval (GstStructure * config, + guint interval) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (interval <= 10000); + + gst_structure_id_set (config, + CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL); +} + +/** + * gst_clapper_config_get_position_update_interval: + * @config: a #GstClapper configuration + * + * Returns: current position update interval in milliseconds + */ +guint +gst_clapper_config_get_position_update_interval (const GstStructure * config) +{ + guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS; + + g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS); + + gst_structure_id_get (config, + CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL); + + return interval; +} + +/** + * gst_clapper_config_set_seek_accurate: + * @config: a #GstClapper configuration + * @accurate: accurate seek or not + * + * Enable or disable accurate seeking. When enabled, elements will try harder + * to seek as accurately as possible to the requested seek position. Generally + * it will be slower especially for formats that don't have any indexes or + * timestamp markers in the stream. + * + * If accurate seeking is disabled, elements will seek as close as the request + * position without slowing down seeking too much. + * + * Accurate seeking is disabled by default. + */ +void +gst_clapper_config_set_seek_accurate (GstStructure * config, gboolean accurate) +{ + g_return_if_fail (config != NULL); + + gst_structure_id_set (config, + CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL); +} + +/** + * gst_clapper_config_get_seek_accurate: + * @config: a #GstClapper configuration + * + * Returns: %TRUE if accurate seeking is enabled + */ +gboolean +gst_clapper_config_get_seek_accurate (const GstStructure * config) +{ + gboolean accurate = FALSE; + + g_return_val_if_fail (config != NULL, FALSE); + + gst_structure_id_get (config, + CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL); + + return accurate; +} + +/** + * gst_clapper_get_video_snapshot: + * @clapper: #GstClapper instance + * @format: output format of the video snapshot + * @config: (allow-none): Additional configuration + * + * Get a snapshot of the currently selected video stream, if any. The format can be + * selected with @format and optional configuration is possible with @config + * Currently supported settings are: + * - width, height of type G_TYPE_INT + * - pixel-aspect-ratio of type GST_TYPE_FRACTION + * Except for GST_CLAPPER_THUMBNAIL_RAW_NATIVE format, if no config is set, pixel-aspect-ratio would be 1/1 + * + * Returns: (transfer full): Current video snapshot sample or %NULL on failure + */ +GstSample * +gst_clapper_get_video_snapshot (GstClapper * self, + GstClapperSnapshotFormat format, const GstStructure * config) +{ + gint video_tracks = 0; + GstSample *sample = NULL; + GstCaps *caps = NULL; + gint width = -1; + gint height = -1; + gint par_n = 1; + gint par_d = 1; + g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); + + g_object_get (self->playbin, "n-video", &video_tracks, NULL); + if (video_tracks == 0) { + GST_DEBUG_OBJECT (self, "total video track num is 0"); + return NULL; + } + + switch (format) { + case GST_CLAPPER_THUMBNAIL_RAW_xRGB: + caps = gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "xRGB", NULL); + break; + case GST_CLAPPER_THUMBNAIL_RAW_BGRx: + caps = gst_caps_new_simple ("video/x-raw", + "format", G_TYPE_STRING, "BGRx", NULL); + break; + case GST_CLAPPER_THUMBNAIL_JPG: + caps = gst_caps_new_empty_simple ("image/jpeg"); + break; + case GST_CLAPPER_THUMBNAIL_PNG: + caps = gst_caps_new_empty_simple ("image/png"); + break; + case GST_CLAPPER_THUMBNAIL_RAW_NATIVE: + default: + caps = gst_caps_new_empty_simple ("video/x-raw"); + break; + } + + if (NULL != config) { + if (!gst_structure_get_int (config, "width", &width)) + width = -1; + if (!gst_structure_get_int (config, "height", &height)) + height = -1; + if (!gst_structure_get_fraction (config, "pixel-aspect-ratio", &par_n, + &par_d)) { + if (format != GST_CLAPPER_THUMBNAIL_RAW_NATIVE) { + par_n = 1; + par_d = 1; + } else { + par_n = 0; + par_d = 0; + } + } + } + + if (width > 0 && height > 0) { + gst_caps_set_simple (caps, "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, NULL); + } + + if (format != GST_CLAPPER_THUMBNAIL_RAW_NATIVE) { + gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + par_n, par_d, NULL); + } else if (NULL != config && par_n != 0 && par_d != 0) { + gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION, + par_n, par_d, NULL); + } + + g_signal_emit_by_name (self->playbin, "convert-sample", caps, &sample); + gst_caps_unref (caps); + if (!sample) { + GST_WARNING_OBJECT (self, "Failed to retrieve or convert video frame"); + return NULL; + } + + return sample; +} diff --git a/lib/gst/clapper/gstclapper.h b/lib/gst/clapper/gstclapper.h new file mode 100644 index 00000000..0ef0b359 --- /dev/null +++ b/lib/gst/clapper/gstclapper.h @@ -0,0 +1,299 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_H__ +#define __GST_CLAPPER_H__ + +#include +#include + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +/* ClapperState */ +GST_CLAPPER_API +GType gst_clapper_state_get_type (void); +#define GST_TYPE_CLAPPER_STATE (gst_clapper_state_get_type ()) + +/** + * GstClapperState: + * @GST_CLAPPER_STATE_STOPPED: clapper is stopped. + * @GST_CLAPPER_STATE_BUFFERING: clapper is buffering. + * @GST_CLAPPER_STATE_PAUSED: clapper is paused. + * @GST_CLAPPER_STATE_PLAYING: clapper is currently playing a stream. + */ +typedef enum +{ + GST_CLAPPER_STATE_STOPPED, + GST_CLAPPER_STATE_BUFFERING, + GST_CLAPPER_STATE_PAUSED, + GST_CLAPPER_STATE_PLAYING +} GstClapperState; + +GST_CLAPPER_API +const gchar * gst_clapper_state_get_name (GstClapperState state); + +/* ClapperError */ +GST_CLAPPER_API +GQuark gst_clapper_error_quark (void); + +GST_CLAPPER_API +GType gst_clapper_error_get_type (void); +#define GST_CLAPPER_ERROR (gst_clapper_error_quark ()) +#define GST_TYPE_CLAPPER_ERROR (gst_clapper_error_get_type ()) + +/** + * GstClapperError: + * @GST_CLAPPER_ERROR_FAILED: generic error. + */ +typedef enum { + GST_CLAPPER_ERROR_FAILED = 0 +} GstClapperError; + +GST_CLAPPER_API +const gchar * gst_clapper_error_get_name (GstClapperError error); + +/* ClapperColorBalanceType */ +GST_CLAPPER_API +GType gst_clapper_color_balance_type_get_type (void); +#define GST_TYPE_CLAPPER_COLOR_BALANCE_TYPE (gst_clapper_color_balance_type_get_type ()) + +/** + * GstClapperColorBalanceType: + * @GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS: brightness or black level. + * @GST_CLAPPER_COLOR_BALANCE_CONTRAST: contrast or luma gain. + * @GST_CLAPPER_COLOR_BALANCE_SATURATION: color saturation or chroma + * gain. + * @GST_CLAPPER_COLOR_BALANCE_HUE: hue or color balance. + */ +typedef enum +{ + GST_CLAPPER_COLOR_BALANCE_BRIGHTNESS, + GST_CLAPPER_COLOR_BALANCE_CONTRAST, + GST_CLAPPER_COLOR_BALANCE_SATURATION, + GST_CLAPPER_COLOR_BALANCE_HUE, +} GstClapperColorBalanceType; + +GST_CLAPPER_API +const gchar * gst_clapper_color_balance_type_get_name (GstClapperColorBalanceType type); + +/* ClapperSnapshotFormat */ + +/** + * GstClapperSnapshotFormat: + * @GST_CLAPPER_THUMBNAIL_RAW_NATIVE: RAW Native. + * @GST_CLAPPER_THUMBNAIL_RAW_xRGB: RAW xRGB. + * @GST_CLAPPER_THUMBNAIL_RAW_BGRx: RAW BGRx. + * @GST_CLAPPER_THUMBNAIL_JPG: JPG. + * @GST_CLAPPER_THUMBNAIL_PNG: PNG. + */ +typedef enum +{ + GST_CLAPPER_THUMBNAIL_RAW_NATIVE = 0, + GST_CLAPPER_THUMBNAIL_RAW_xRGB, + GST_CLAPPER_THUMBNAIL_RAW_BGRx, + GST_CLAPPER_THUMBNAIL_JPG, + GST_CLAPPER_THUMBNAIL_PNG +} GstClapperSnapshotFormat; + +#define GST_TYPE_CLAPPER (gst_clapper_get_type ()) +#define GST_IS_CLAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER)) +#define GST_IS_CLAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER)) +#define GST_CLAPPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER, GstClapperClass)) +#define GST_CLAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER, GstClapper)) +#define GST_CLAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER, GstClapperClass)) +#define GST_CLAPPER_CAST(obj) ((GstClapper*)(obj)) + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstClapper, gst_object_unref) +#endif + +GST_CLAPPER_API +GType gst_clapper_get_type (void); + +GST_CLAPPER_API +GstClapper * gst_clapper_new (GstClapperVideoRenderer *video_renderer, GstClapperSignalDispatcher *signal_dispatcher); + +GST_CLAPPER_API +void gst_clapper_play (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_pause (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_stop (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_seek (GstClapper *clapper, GstClockTime position); + +GST_CLAPPER_API +void gst_clapper_set_rate (GstClapper *clapper, gdouble rate); + +GST_CLAPPER_API +gdouble gst_clapper_get_rate (GstClapper *clapper); + +GST_CLAPPER_API +gchar * gst_clapper_get_uri (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_uri (GstClapper *clapper, const gchar *uri); + +GST_CLAPPER_API +gchar * gst_clapper_get_subtitle_uri (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_subtitle_uri (GstClapper *clapper, const gchar *uri); + +GST_CLAPPER_API +GstClockTime gst_clapper_get_position (GstClapper *clapper); + +GST_CLAPPER_API +GstClockTime gst_clapper_get_duration (GstClapper *clapper); + +GST_CLAPPER_API +gdouble gst_clapper_get_volume (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_volume (GstClapper *clapper, gdouble val); + +GST_CLAPPER_API +gboolean gst_clapper_get_mute (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_mute (GstClapper *clapper, gboolean val); + +GST_CLAPPER_API +GstElement * gst_clapper_get_pipeline (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_video_track_enabled (GstClapper *clapper, gboolean enabled); + +GST_CLAPPER_API +void gst_clapper_set_audio_track_enabled (GstClapper *clapper, gboolean enabled); + +GST_CLAPPER_API +void gst_clapper_set_subtitle_track_enabled (GstClapper *clapper, gboolean enabled); + +GST_CLAPPER_API +gboolean gst_clapper_set_audio_track (GstClapper *clapper, gint stream_index); + +GST_CLAPPER_API +gboolean gst_clapper_set_video_track (GstClapper *clapper, gint stream_index); + +GST_CLAPPER_API +gboolean gst_clapper_set_subtitle_track (GstClapper *clapper, gint stream_index); + +GST_CLAPPER_API +GstClapperMediaInfo * + gst_clapper_get_media_info (GstClapper *clapper); + +GST_CLAPPER_API +GstClapperAudioInfo * + gst_clapper_get_current_audio_track (GstClapper *clapper); + +GST_CLAPPER_API +GstClapperVideoInfo * + gst_clapper_get_current_video_track (GstClapper *clapper); + +GST_CLAPPER_API +GstClapperSubtitleInfo * + gst_clapper_get_current_subtitle_track (GstClapper *clapper); + +GST_CLAPPER_API +gboolean gst_clapper_set_visualization (GstClapper *clapper, const gchar *name); + +GST_CLAPPER_API +void gst_clapper_set_visualization_enabled (GstClapper *clapper, gboolean enabled); + +GST_CLAPPER_API +gchar * gst_clapper_get_current_visualization (GstClapper *clapper); + +GST_CLAPPER_API +gboolean gst_clapper_has_color_balance (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_color_balance (GstClapper *clapper, GstClapperColorBalanceType type, gdouble value); + +GST_CLAPPER_API +gdouble gst_clapper_get_color_balance (GstClapper *clapper, GstClapperColorBalanceType type); + +GST_CLAPPER_API +GstVideoMultiviewFramePacking + gst_clapper_get_multiview_mode (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_multiview_mode (GstClapper *clapper, GstVideoMultiviewFramePacking mode); + +GST_CLAPPER_API +GstVideoMultiviewFlags + gst_clapper_get_multiview_flags (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_multiview_flags (GstClapper *clapper, GstVideoMultiviewFlags flags); + +GST_CLAPPER_API +gint64 gst_clapper_get_audio_video_offset (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_audio_video_offset (GstClapper *clapper, gint64 offset); + +GST_CLAPPER_API +gint64 gst_clapper_get_subtitle_video_offset (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_subtitle_video_offset (GstClapper *clapper, gint64 offset); + +GST_CLAPPER_API +gboolean gst_clapper_set_config (GstClapper *clapper, GstStructure *config); + +GST_CLAPPER_API +GstStructure * + gst_clapper_get_config (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_config_set_user_agent (GstStructure *config, const gchar *agent); + +GST_CLAPPER_API +gchar * gst_clapper_config_get_user_agent (const GstStructure *config); + +GST_CLAPPER_API +void gst_clapper_config_set_position_update_interval (GstStructure *config, guint interval); + +GST_CLAPPER_API +guint gst_clapper_config_get_position_update_interval (const GstStructure *config); + +GST_CLAPPER_API +void gst_clapper_config_set_seek_accurate (GstStructure *config, gboolean accurate); + +GST_CLAPPER_API +gboolean gst_clapper_config_get_seek_accurate (const GstStructure *config); + +GST_CLAPPER_API +GstSample * gst_clapper_get_video_snapshot (GstClapper *clapper, GstClapperSnapshotFormat format, const GstStructure *config); + +G_END_DECLS + +#endif /* __GST_CLAPPER_H__ */ diff --git a/lib/gst/clapper/meson.build b/lib/gst/clapper/meson.build new file mode 100644 index 00000000..fc93b596 --- /dev/null +++ b/lib/gst/clapper/meson.build @@ -0,0 +1,51 @@ +gstclapper_sources = [ + 'gstclapper.c', + 'gstclapper-signal-dispatcher.c', + 'gstclapper-video-renderer.c', + 'gstclapper-media-info.c', + 'gstclapper-g-main-context-signal-dispatcher.c', + 'gstclapper-video-overlay-video-renderer.c', + 'gstclapper-visualization.c', +] + +gstclapper_headers = [ + 'clapper.h', + 'clapper-prelude.h', + 'gstclapper.h', + 'gstclapper-types.h', + 'gstclapper-signal-dispatcher.h', + 'gstclapper-video-renderer.h', + 'gstclapper-media-info.h', + 'gstclapper-g-main-context-signal-dispatcher.h', + 'gstclapper-video-overlay-video-renderer.h', + 'gstclapper-visualization.h', +] + +if not build_gir + error('Clapper requires GI bindings to be compiled') +endif + +gstclapper = library('gstclapper-' + api_version, + gstclapper_sources, + c_args : gst_clapper_args + ['-DBUILDING_GST_CLAPPER'], + include_directories : [configinc, libsinc], + version : libversion, + install : true, + dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep, + gsttag_dep, gstpbutils_dep], +) + +clapper_gir = gnome.generate_gir(gstclapper, + sources : gstclapper_sources + gstclapper_headers, + namespace : 'GstClapper', + nsversion : api_version, + identifier_prefix : 'Gst', + symbol_prefix : 'gst', + export_packages : 'gstreamer-clapper-1.0', + includes : ['Gst-1.0', 'GstPbutils-1.0', 'GstBase-1.0', 'GstVideo-1.0', + 'GstAudio-1.0', 'GstTag-1.0'], + install : true, + extra_args : gir_init_section + ['-DGST_USE_UNSTABLE_API'], + dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep, + gsttag_dep, gstpbutils_dep] +) diff --git a/lib/gst/meson.build b/lib/gst/meson.build new file mode 100644 index 00000000..90cf6581 --- /dev/null +++ b/lib/gst/meson.build @@ -0,0 +1 @@ +subdir('clapper') diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 00000000..a14f258d --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,237 @@ +glib_req = '>= 2.56.0' +gst_req = '>= 1.18.0' + +api_version = '1.0' +libversion = meson.project_version() + +cc = meson.get_compiler('c') +cxx = meson.get_compiler('cpp') + +plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0') +cdata = configuration_data() + +if cc.get_id() == 'msvc' + msvc_args = [ + # Ignore several spurious warnings for things gstreamer does very commonly + # If a warning is completely useless and spammy, use '/wdXXXX' to suppress it + # If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once + # NOTE: Only add warnings here if you are sure they're spurious + '/wd4018', # implicit signed/unsigned conversion + '/wd4146', # unary minus on unsigned (beware INT_MIN) + '/wd4244', # lossy type conversion (e.g. double -> int) + '/wd4305', # truncating type conversion (e.g. double -> float) + cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8 + + # Enable some warnings on MSVC to match GCC/Clang behaviour + '/w14062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled + '/w14101', # 'identifier' : unreferenced local variable + '/w14189', # 'identifier' : local variable is initialized but not referenced + ] + add_project_arguments(msvc_args, language: ['c', 'cpp']) +else + if cxx.has_argument('-Wno-non-virtual-dtor') + add_project_arguments('-Wno-non-virtual-dtor', language: 'cpp') + endif +endif + +if cc.has_link_argument('-Wl,-Bsymbolic-functions') + add_project_link_arguments('-Wl,-Bsymbolic-functions', language : 'c') +endif + +# Symbol visibility +if cc.get_id() == 'msvc' + export_define = '__declspec(dllexport) extern' +elif cc.has_argument('-fvisibility=hidden') + add_project_arguments('-fvisibility=hidden', language: 'c') + add_project_arguments('-fvisibility=hidden', language: 'cpp') + export_define = 'extern __attribute__ ((visibility ("default")))' +else + export_define = 'extern' +endif + +# Passing this through the command line would be too messy +cdata.set('GST_API_EXPORT', export_define) + +# Disable strict aliasing +if cc.has_argument('-fno-strict-aliasing') + add_project_arguments('-fno-strict-aliasing', language: 'c') +endif +if cxx.has_argument('-fno-strict-aliasing') + add_project_arguments('-fno-strict-aliasing', language: 'cpp') +endif + +if not get_option('deprecated-glib-api') + message('Disabling deprecated GLib API') + add_project_arguments('-DG_DISABLE_DEPRECATED', language: 'c') +endif + +if not get_option('devel-checks') + message('Disabling GLib cast checks') + add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: 'c') + + message('Disabling GLib asserts') + add_project_arguments('-DG_DISABLE_ASSERT', language: 'c') + + message('Disabling GLib checks') + add_project_arguments('-DG_DISABLE_CHECKS', language: 'c') +endif + +check_headers = [ + ['HAVE_DLFCN_H', 'dlfcn.h'], + ['HAVE_FCNTL_H', 'fcntl.h'], + ['HAVE_INTTYPES_H', 'inttypes.h'], + ['HAVE_MEMORY_H', 'memory.h'], + ['HAVE_NETINET_IN_H', 'netinet/in.h'], + ['HAVE_NETINET_IP_H', 'netinet/ip.h'], + ['HAVE_NETINET_TCP_H', 'netinet/tcp.h'], + ['HAVE_PTHREAD_H', 'pthread.h'], + ['HAVE_STDINT_H', 'stdint.h'], + ['HAVE_STDLIB_H', 'stdlib.h'], + ['HAVE_STRINGS_H', 'strings.h'], + ['HAVE_STRING_H', 'string.h'], + ['HAVE_SYS_PARAM_H', 'sys/param.h'], + ['HAVE_SYS_SOCKET_H', 'sys/socket.h'], + ['HAVE_SYS_STAT_H', 'sys/stat.h'], + ['HAVE_SYS_TIME_H', 'sys/time.h'], + ['HAVE_SYS_TYPES_H', 'sys/types.h'], + ['HAVE_SYS_UTSNAME_H', 'sys/utsname.h'], + ['HAVE_UNISTD_H', 'unistd.h'], +] + +foreach h : check_headers + if cc.has_header(h.get(1)) + cdata.set(h.get(0), 1) + endif +endforeach + +check_functions = [ + ['HAVE_DCGETTEXT', 'dcgettext'], + ['HAVE_GETPAGESIZE', 'getpagesize'], + ['HAVE_GMTIME_R', 'gmtime_r'], + ['HAVE_MEMFD_CREATE', 'memfd_create'], + ['HAVE_MMAP', 'mmap'], + ['HAVE_PIPE2', 'pipe2'], + ['HAVE_GETRUSAGE', 'getrusage', '#include'], +] + +foreach f : check_functions + prefix = '' + if f.length() == 3 + prefix = f.get(2) + endif + if cc.has_function(f.get(1), prefix: prefix) + cdata.set(f.get(0), 1) + endif +endforeach + +cdata.set('SIZEOF_CHAR', cc.sizeof('char')) +cdata.set('SIZEOF_INT', cc.sizeof('int')) +cdata.set('SIZEOF_LONG', cc.sizeof('long')) +cdata.set('SIZEOF_SHORT', cc.sizeof('short')) +cdata.set('SIZEOF_VOIDP', cc.sizeof('void*')) + +cdata.set_quoted('VERSION', libversion) +cdata.set_quoted('PACKAGE', 'gst-plugins-clapper') +cdata.set_quoted('PACKAGE_VERSION', libversion) +cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/Rafostar/clapper/issues/new') +cdata.set_quoted('PACKAGE_NAME', 'GStreamer Clapper Libs') +cdata.set_quoted('GST_API_VERSION', api_version) +cdata.set_quoted('GST_LICENSE', 'LGPL') +cdata.set_quoted('LIBDIR', join_paths(get_option('prefix'), get_option('libdir'))) +cdata.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) + +warning_flags = [ + '-Wmissing-declarations', + '-Wredundant-decls', + '-Wwrite-strings', + '-Wformat', + '-Wformat-security', + '-Winit-self', + '-Wmissing-include-dirs', + '-Waddress', + '-Wno-multichar', + '-Wvla', + '-Wpointer-arith', +] + +warning_c_flags = [ + '-Wmissing-prototypes', + '-Wdeclaration-after-statement', + '-Wold-style-definition', +] + +warning_cxx_flags = [ + '-Wformat-nonliteral', +] + +foreach extra_arg : warning_c_flags + if cc.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'c') + endif +endforeach + +foreach extra_arg : warning_cxx_flags + if cxx.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'cpp') + endif +endforeach + +foreach extra_arg : warning_flags + if cc.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'c') + endif + if cxx.has_argument (extra_arg) + add_project_arguments([extra_arg], language: 'cpp') + endif +endforeach + +cdata.set_quoted('GST_PACKAGE_NAME', 'GStreamer Plugins Clapper') +cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') + +# Mandatory GST deps +gst_dep = dependency('gstreamer-1.0', version : gst_req, + fallback : ['gstreamer', 'gst_dep']) +gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req, + fallback : ['gstreamer', 'gst_base_dep']) +gstpbutils_dep = dependency('gstreamer-pbutils-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'pbutils_dep']) +gstaudio_dep = dependency('gstreamer-audio-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'audio_dep']) +gsttag_dep = dependency('gstreamer-tag-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'tag_dep']) +gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'video_dep']) + +libm = cc.find_library('m', required : false) +glib_dep = dependency('glib-2.0', version : glib_req, fallback: ['glib', 'libglib_dep']) +gmodule_dep = dependency('gmodule-2.0', fallback: ['glib', 'libgmodule_dep']) +gio_dep = dependency('gio-2.0', fallback: ['glib', 'libgio_dep']) + +cdata.set('DISABLE_ORC', 1) +cdata.set('GST_ENABLE_EXTRA_CHECKS', get_option('devel-checks')) +cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', 'Unknown') + +message('GStreamer debug system is disabled') +if cc.has_argument('-Wno-unused') + add_project_arguments('-Wno-unused', language: 'c') +endif +if cxx.has_argument ('-Wno-unused') + add_project_arguments('-Wno-unused', language: 'cpp') +endif + +gst_clapper_args = ['-DHAVE_CONFIG_H'] +configinc = include_directories('.') +libsinc = include_directories('gst') + +python3 = import('python').find_installation() +gnome = import('gnome') +gir = find_program('g-ir-scanner', required : true) +build_gir = gir.found() +gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \ + 'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \ + 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ + 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ + 'gst_init(NULL,NULL);', '--quiet' +] +subdir('gst') +configure_file(output : 'config.h', configuration : cdata) diff --git a/meson.build b/meson.build index 225922a0..f9cc8488 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('com.github.rafostar.Clapper', +project('com.github.rafostar.Clapper', 'c', 'cpp', version: '0.0.0', meson_version: '>= 0.50.0', license: 'GPL3', @@ -9,19 +9,25 @@ python = import('python') python_bin = python.find_installation('python3') if not python_bin.found() - error('No valid python3 binary found') + error('No valid python3 binary found') endif conf = configuration_data() conf.set('bindir', join_paths(get_option('prefix'), 'bin')) -subdir('bin') -subdir('data') +if get_option('clapper-lib') + subdir('lib') +endif -installdir = join_paths(get_option('prefix'), 'share', meson.project_name()) -install_subdir('src', install_dir : installdir) -install_subdir('extras', install_dir : installdir) -install_subdir('css', install_dir : installdir) -install_subdir('ui', install_dir : installdir) +if get_option('clapper-player') + subdir('bin') + subdir('data') -meson.add_install_script('build-aux/meson/postinstall.py') + installdir = join_paths(get_option('prefix'), 'share', meson.project_name()) + install_subdir('src', install_dir : installdir) + install_subdir('extras', install_dir : installdir) + install_subdir('css', install_dir : installdir) + install_subdir('ui', install_dir : installdir) + + meson.add_install_script('build-aux/meson/postinstall.py') +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..2bfc6599 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,20 @@ +option('clapper-player', + type : 'boolean', + value : true, + description: 'Build Clapper player' +) +option('clapper-lib', + type : 'boolean', + value : true, + description: 'Build Clapper libs (including API)' +) +option('devel-checks', + type : 'boolean', + value : false, + description: 'GStreamer GLib checks and asserts such as API guards (disable for stable releases)' +) +option('deprecated-glib-api', + type : 'boolean', + value : true, + description: 'Allow using of deprecated GLib API' +) From bee188937625c0ea72b5bb236f79f94d1dd5f25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Wed, 27 Jan 2021 22:07:17 +0100 Subject: [PATCH 02/19] Port app to the new GstClapper API --- src/main.js | 4 ++-- src/misc.js | 4 ++-- src/player.js | 18 +++++++++--------- src/playerBase.js | 26 +++++++++++++------------- src/widget.js | 20 ++++++++++---------- src/widgetRemote.js | 8 ++++---- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main.js b/src/main.js index 15bec1ab..76d169c4 100644 --- a/src/main.js +++ b/src/main.js @@ -2,10 +2,10 @@ imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; const { Gst } = imports.gi; -const { App } = imports.src.app; - Gst.init(null); +const { App } = imports.src.app; + function main(argv) { new App().run(argv); diff --git a/src/misc.js b/src/misc.js index 79ad654a..f8d5458f 100644 --- a/src/misc.js +++ b/src/misc.js @@ -1,4 +1,4 @@ -const { Gio, GstAudio, GstPlayer, Gdk, Gtk } = imports.gi; +const { Gio, GstAudio, GstClapper, Gdk, Gtk } = imports.gi; const Debug = imports.src.debug; const { debug } = Debug; @@ -52,7 +52,7 @@ function inhibitForState(state, window) { let isInhibited = false; - if(state === GstPlayer.PlayerState.PLAYING) { + if(state === GstClapper.ClapperState.PLAYING) { if(inhibitCookie) return; diff --git a/src/player.js b/src/player.js index bad6f1fd..21f31bd0 100644 --- a/src/player.js +++ b/src/player.js @@ -1,4 +1,4 @@ -const { Gdk, Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi; +const { Gdk, Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi; const ByteArray = imports.byteArray; const Debug = imports.src.debug; const Misc = imports.src.misc; @@ -188,7 +188,7 @@ class ClapperPlayer extends PlayerBase this.seek_done = false; - if(this.state === GstPlayer.PlayerState.STOPPED) + if(this.state === GstClapper.ClapperState.STOPPED) this.pause(); if(position < 0) @@ -213,7 +213,7 @@ class ClapperPlayer extends PlayerBase /* FIXME: Remove this check when GstPlay(er) have set_seek_mode function */ if(this.set_seek_mode) { - this.set_seek_mode(GstPlayer.PlayerSeekMode.DEFAULT); + this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); this.seekingMode = 'normal'; this.needsFastSeekRestore = true; } @@ -275,7 +275,7 @@ class ClapperPlayer extends PlayerBase toggle_play() { - const action = (this.state === GstPlayer.PlayerState.PLAYING) + const action = (this.state === GstClapper.ClapperState.PLAYING) ? 'pause' : 'play'; @@ -387,11 +387,11 @@ class ClapperPlayer extends PlayerBase this.state = state; this.emitWs('state_changed', state); - if(state !== GstPlayer.PlayerState.BUFFERING) { + if(state !== GstClapper.ClapperState.BUFFERING) { const root = player.widget.get_root(); if(this.quitOnStop) { - if(root && state === GstPlayer.PlayerState.STOPPED) + if(root && state === GstClapper.ClapperState.STOPPED) root.run_dispose(); return; @@ -402,11 +402,11 @@ class ClapperPlayer extends PlayerBase const clapperWidget = player.widget.get_ancestor(Gtk.Grid); if(!clapperWidget) return; - if(!this.seek_done && state !== GstPlayer.PlayerState.BUFFERING) { + if(!this.seek_done && state !== GstClapper.ClapperState.BUFFERING) { clapperWidget.updateTime(); if(this.needsFastSeekRestore) { - this.set_seek_mode(GstPlayer.PlayerSeekMode.FAST); + this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); this.seekingMode = 'fast'; this.needsFastSeekRestore = false; } @@ -726,7 +726,7 @@ class ClapperPlayer extends PlayerBase { this._performCloseCleanup(window); - if(this.state === GstPlayer.PlayerState.STOPPED) + if(this.state === GstClapper.ClapperState.STOPPED) return window.run_dispose(); this.quitOnStop = true; diff --git a/src/playerBase.js b/src/playerBase.js index a2287e46..048fac3b 100644 --- a/src/playerBase.js +++ b/src/playerBase.js @@ -1,4 +1,4 @@ -const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi; +const { Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi; const Debug = imports.src.debug; const Misc = imports.src.misc; const { PlaylistWidget } = imports.src.playlist; @@ -10,7 +10,7 @@ const { settings } = Misc; let WebServer; var PlayerBase = GObject.registerClass( -class ClapperPlayerBase extends GstPlayer.Player +class ClapperPlayerBase extends GstClapper.Clapper { _init() { @@ -34,10 +34,10 @@ class ClapperPlayerBase extends GstPlayer.Player const acquired = context.acquire(); debug(`default context acquired: ${acquired}`); - const dispatcher = new GstPlayer.PlayerGMainContextSignalDispatcher({ + const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher({ application_context: context, }); - const renderer = new GstPlayer.PlayerVideoOverlayVideoRenderer({ + const renderer = new GstClapper.ClapperVideoOverlayVideoRenderer({ video_sink: glsinkbin }); @@ -50,7 +50,7 @@ class ClapperPlayerBase extends GstPlayer.Player this.widget.vexpand = true; this.widget.hexpand = true; - this.state = GstPlayer.PlayerState.STOPPED; + this.state = GstClapper.ClapperState.STOPPED; this.visualization_enabled = false; this.webserver = null; @@ -87,13 +87,13 @@ class ClapperPlayerBase extends GstPlayer.Player set_initial_config() { - const gstPlayerConfig = { + const gstClapperConfig = { position_update_interval: 1000, user_agent: 'clapper', }; - for(let option of Object.keys(gstPlayerConfig)) - this.set_config_option(option, gstPlayerConfig[option]); + for(let option of Object.keys(gstClapperConfig)) + this.set_config_option(option, gstClapperConfig[option]); this.set_mute(false); @@ -104,7 +104,7 @@ class ClapperPlayerBase extends GstPlayer.Player set_config_option(option, value) { - const setOption = GstPlayer.Player[`config_set_${option}`]; + const setOption = GstClapper.Clapper[`config_set_${option}`]; if(!setOption) return debug(`unsupported option: ${option}`, 'LEVEL_WARNING'); @@ -158,7 +158,7 @@ class ClapperPlayerBase extends GstPlayer.Player { this.widget.ignore_textures = isEnabled; - if(this.state !== GstPlayer.PlayerState.PLAYING) + if(this.state !== GstClapper.ClapperState.PLAYING) this.widget.queue_render(); } @@ -186,13 +186,13 @@ class ClapperPlayerBase extends GstPlayer.Player switch(this.seekingMode) { case 'fast': if(isSeekMode) - this.set_seek_mode(GstPlayer.PlayerSeekMode.FAST); + this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); else this.set_config_option('seek_fast', true); break; case 'accurate': if(isSeekMode) - this.set_seek_mode(GstPlayer.PlayerSeekMode.ACCURATE); + this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE); else { this.set_config_option('seek_fast', false); this.set_config_option('seek_accurate', true); @@ -200,7 +200,7 @@ class ClapperPlayerBase extends GstPlayer.Player break; default: if(isSeekMode) - this.set_seek_mode(GstPlayer.PlayerSeekMode.DEFAULT); + this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); else { this.set_config_option('seek_fast', false); this.set_config_option('seek_accurate', false); diff --git a/src/widget.js b/src/widget.js index 970910e4..48fab83c 100644 --- a/src/widget.js +++ b/src/widget.js @@ -1,4 +1,4 @@ -const { Gdk, GLib, GObject, GstPlayer, Gtk } = imports.gi; +const { Gdk, GLib, GObject, GstClapper, Gtk } = imports.gi; const { Controls } = imports.src.controls; const Debug = imports.src.debug; const Misc = imports.src.misc; @@ -237,7 +237,7 @@ class ClapperWidget extends Gtk.Grid let type, text, codec; switch(info.constructor) { - case GstPlayer.PlayerVideoInfo: + case GstClapper.ClapperVideoInfo: type = 'video'; codec = info.get_codec() || 'Undetermined'; text = codec + ', ' + @@ -248,7 +248,7 @@ class ClapperWidget extends Gtk.Grid if(fps) text += `@${fps}`; break; - case GstPlayer.PlayerAudioInfo: + case GstClapper.ClapperAudioInfo: type = 'audio'; codec = info.get_codec() || 'Undetermined'; if(codec.includes('(')) { @@ -261,7 +261,7 @@ class ClapperWidget extends Gtk.Grid text += ', ' + codec + ', ' + info.get_channels() + ' Channels'; break; - case GstPlayer.PlayerSubtitleInfo: + case GstClapper.ClapperSubtitleInfo: type = 'subtitle'; text = info.get_language() || 'Undetermined'; break; @@ -408,7 +408,7 @@ class ClapperWidget extends Gtk.Grid { if(isShow && !this.controls.visualizationsButton.isVisList) { debug('creating visualizations list'); - const visArr = GstPlayer.Player.visualizations_get(); + const visArr = GstClapper.Clapper.visualizations_get(); if(!visArr.length) return; @@ -446,7 +446,7 @@ class ClapperWidget extends Gtk.Grid _onPlayerStateChanged(player, state) { switch(state) { - case GstPlayer.PlayerState.BUFFERING: + case GstClapper.ClapperState.BUFFERING: debug('player state changed to: BUFFERING'); if(player.needsTocUpdate) { this.controls._setChapterVisible(false); @@ -457,18 +457,18 @@ class ClapperWidget extends Gtk.Grid this.needsTracksUpdate = true; } break; - case GstPlayer.PlayerState.STOPPED: + case GstClapper.ClapperState.STOPPED: debug('player state changed to: STOPPED'); this.controls.currentPosition = 0; this.controls.positionScale.set_value(0); this.controls.togglePlayButton.setPrimaryIcon(); this.needsTracksUpdate = true; break; - case GstPlayer.PlayerState.PAUSED: + case GstClapper.ClapperState.PAUSED: debug('player state changed to: PAUSED'); this.controls.togglePlayButton.setPrimaryIcon(); break; - case GstPlayer.PlayerState.PLAYING: + case GstClapper.ClapperState.PLAYING: debug('player state changed to: PLAYING'); this.controls.togglePlayButton.setSecondaryIcon(); if(this.needsTracksUpdate) { @@ -483,7 +483,7 @@ class ClapperWidget extends Gtk.Grid break; } - const isNotStopped = (state !== GstPlayer.PlayerState.STOPPED); + const isNotStopped = (state !== GstClapper.ClapperState.STOPPED); this.revealerTop.endTime.set_visible(isNotStopped); } diff --git a/src/widgetRemote.js b/src/widgetRemote.js index a79ad4a7..8bf3288f 100644 --- a/src/widgetRemote.js +++ b/src/widgetRemote.js @@ -1,4 +1,4 @@ -const { GObject, Gtk, GstPlayer } = imports.gi; +const { GObject, Gtk, GstClapper } = imports.gi; const Buttons = imports.src.buttons; const Misc = imports.src.misc; const { PlayerRemote } = imports.src.playerRemote; @@ -50,11 +50,11 @@ class ClapperWidgetRemote extends Gtk.Grid switch(action) { case 'state_changed': switch(value) { - case GstPlayer.PlayerState.STOPPED: - case GstPlayer.PlayerState.PAUSED: + case GstClapper.ClapperState.STOPPED: + case GstClapper.ClapperState.PAUSED: this.togglePlayButton.setPrimaryIcon(); break; - case GstPlayer.PlayerState.PLAYING: + case GstClapper.ClapperState.PLAYING: this.togglePlayButton.setSecondaryIcon(); break; default: From 5cc312130dc0ffe5fc56e16d5f13bcb1ddbe6dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Wed, 27 Jan 2021 22:58:51 +0100 Subject: [PATCH 03/19] API: set seek mode without stopping playback --- lib/gst/clapper/gstclapper.c | 118 ++++++++++++++++++++++------------- lib/gst/clapper/gstclapper.h | 31 +++++++-- 2 files changed, 99 insertions(+), 50 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 73256376..16d0ac14 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -58,6 +58,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_clapper_debug); #define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100 #define DEFAULT_AUDIO_VIDEO_OFFSET 0 #define DEFAULT_SUBTITLE_VIDEO_OFFSET 0 +#define DEFAULT_SEEK_MODE GST_CLAPPER_SEEK_MODE_DEFAULT /** * gst_clapper_error_quark: @@ -75,7 +76,6 @@ typedef enum { CONFIG_QUARK_USER_AGENT = 0, CONFIG_QUARK_POSITION_INTERVAL_UPDATE, - CONFIG_QUARK_ACCURATE_SEEK, CONFIG_QUARK_MAX } ConfigQuarkId; @@ -83,7 +83,6 @@ typedef enum static const gchar *_config_quark_strings[] = { "user-agent", "position-interval-update", - "accurate-seek", }; GQuark _config_quark_table[CONFIG_QUARK_MAX]; @@ -111,6 +110,7 @@ enum PROP_VIDEO_MULTIVIEW_FLAGS, PROP_AUDIO_VIDEO_OFFSET, PROP_SUBTITLE_VIDEO_OFFSET, + PROP_SEEK_MODE, PROP_LAST }; @@ -176,6 +176,8 @@ struct _GstClapper GstStructure *config; + GstClapperSeekMode seek_mode; + /* Protected by lock */ gboolean seek_pending; /* Only set from main context */ GstClockTime last_seek_time; /* Only set from main context */ @@ -282,7 +284,6 @@ gst_clapper_init (GstClapper * self) /* *INDENT-OFF* */ self->config = gst_structure_new_id (QUARK_CONFIG, CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS, - CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE, NULL); /* *INDENT-ON* */ @@ -412,6 +413,12 @@ gst_clapper_class_init (GstClapperClass * klass) "The synchronisation offset between text and video in nanoseconds", G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + param_specs[PROP_SEEK_MODE] = + g_param_spec_enum ("seek-mode", "Clapper Seek Mode", + "Selected seek mode to use when performing seeks", + GST_TYPE_CLAPPER_SEEK_MODE, DEFAULT_SEEK_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); signals[SIGNAL_URI_LOADED] = @@ -735,6 +742,11 @@ gst_clapper_set_property (GObject * object, guint prop_id, case PROP_SUBTITLE_VIDEO_OFFSET: g_object_set_property (G_OBJECT (self->playbin), "text-offset", value); break; + case PROP_SEEK_MODE: + g_mutex_lock (&self->lock); + self->seek_mode = g_value_get_enum (value); + g_mutex_unlock (&self->lock); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -835,6 +847,11 @@ gst_clapper_get_property (GObject * object, guint prop_id, case PROP_SUBTITLE_VIDEO_OFFSET: g_object_get_property (G_OBJECT (self->playbin), "text-offset", value); break; + case PROP_SEEK_MODE: + g_mutex_lock (&self->lock); + g_value_set_enum (value, self->seek_mode); + g_mutex_unlock (&self->lock); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2972,6 +2989,7 @@ gst_clapper_main (gpointer data) self->is_eos = FALSE; self->is_live = FALSE; self->rate = 1.0; + self->seek_mode = DEFAULT_SEEK_MODE; GST_TRACE_OBJECT (self, "Starting main loop"); g_main_loop_run (self->loop); @@ -3299,8 +3317,8 @@ gst_clapper_seek_internal_locked (GstClapper * self) gdouble rate; GstStateChangeReturn state_ret; GstEvent *s_event; + GstClapperSeekMode seek_mode; GstSeekFlags flags = 0; - gboolean accurate = FALSE; remove_seek_source (self); @@ -3313,8 +3331,6 @@ gst_clapper_seek_internal_locked (GstClapper * self) if (state_ret == GST_STATE_CHANGE_FAILURE) { emit_error (self, g_error_new (GST_CLAPPER_ERROR, GST_CLAPPER_ERROR_FAILED, "Failed to seek")); - g_mutex_lock (&self->lock); - return; } g_mutex_lock (&self->lock); return; @@ -3325,6 +3341,7 @@ gst_clapper_seek_internal_locked (GstClapper * self) self->seek_position = GST_CLOCK_TIME_NONE; self->seek_pending = TRUE; rate = self->rate; + seek_mode = self->seek_mode; g_mutex_unlock (&self->lock); remove_tick_source (self); @@ -3332,17 +3349,19 @@ gst_clapper_seek_internal_locked (GstClapper * self) flags |= GST_SEEK_FLAG_FLUSH; - accurate = gst_clapper_config_get_seek_accurate (self->config); - - if (accurate) { - flags |= GST_SEEK_FLAG_ACCURATE; - } else { - flags &= ~GST_SEEK_FLAG_ACCURATE; + switch (seek_mode) { + case GST_CLAPPER_SEEK_MODE_ACCURATE: + flags |= GST_SEEK_FLAG_ACCURATE; + break; + case GST_CLAPPER_SEEK_MODE_FAST: + flags |= GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_AFTER; + break; + default: + break; } - if (rate != 1.0) { + if (rate != 1.0) flags |= GST_SEEK_FLAG_TRICKMODE; - } if (rate >= 0.0) { s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, @@ -4650,47 +4669,58 @@ gst_clapper_config_get_position_update_interval (const GstStructure * config) return interval; } -/** - * gst_clapper_config_set_seek_accurate: - * @config: a #GstClapper configuration - * @accurate: accurate seek or not - * - * Enable or disable accurate seeking. When enabled, elements will try harder - * to seek as accurately as possible to the requested seek position. Generally - * it will be slower especially for formats that don't have any indexes or - * timestamp markers in the stream. - * - * If accurate seeking is disabled, elements will seek as close as the request - * position without slowing down seeking too much. - * - * Accurate seeking is disabled by default. - */ -void -gst_clapper_config_set_seek_accurate (GstStructure * config, gboolean accurate) +GType +gst_clapper_seek_mode_get_type (void) { - g_return_if_fail (config != NULL); + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_SEEK_MODE_DEFAULT), "GST_CLAPPER_SEEK_MODE_DEFAULT", + "default"}, + {C_ENUM (GST_CLAPPER_SEEK_MODE_ACCURATE), "GST_CLAPPER_SEEK_MODE_ACCURATE", + "accurate"}, + {C_ENUM (GST_CLAPPER_SEEK_MODE_FAST), "GST_CLAPPER_SEEK_MODE_FAST", "fast"}, + {0, NULL, NULL} + }; - gst_structure_id_set (config, - CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL); + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperSeekMode", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; } /** - * gst_clapper_config_get_seek_accurate: - * @config: a #GstClapper configuration + * gst_clapper_get_seek_mode: + * @clapper: #GstClapper instance * - * Returns: %TRUE if accurate seeking is enabled + * Returns: The currently used seek mode, Default: 0 "default" */ -gboolean -gst_clapper_config_get_seek_accurate (const GstStructure * config) +GstClapperSeekMode +gst_clapper_get_seek_mode (GstClapper * self) { - gboolean accurate = FALSE; + GstClapperSeekMode mode; - g_return_val_if_fail (config != NULL, FALSE); + g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_SEEK_MODE); - gst_structure_id_get (config, - CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL); + g_object_get (self, "seek-mode", &mode, NULL); - return accurate; + return mode; +} + +/** + * gst_clapper_set_seek_mode: + * @clapper: #GstClapper instance + * @mode: #GstClapperSeekMode + * + * Changes currently used clapper seek mode to the one of @mode + */ +void +gst_clapper_set_seek_mode (GstClapper * self, GstClapperSeekMode mode) +{ + g_return_if_fail (GST_IS_CLAPPER (self)); + + g_object_set (self, "seek-mode", mode, NULL); } /** diff --git a/lib/gst/clapper/gstclapper.h b/lib/gst/clapper/gstclapper.h index 0ef0b359..9ee6068e 100644 --- a/lib/gst/clapper/gstclapper.h +++ b/lib/gst/clapper/gstclapper.h @@ -56,6 +56,24 @@ typedef enum GST_CLAPPER_API const gchar * gst_clapper_state_get_name (GstClapperState state); +/* ClapperSeekMode */ +GST_CLAPPER_API +GType gst_clapper_seek_mode_get_type (void); +#define GST_TYPE_CLAPPER_SEEK_MODE (gst_clapper_seek_mode_get_type ()) + +/** + * GstClapperSeekMode: + * @GST_CLAPPER_SEEK_MODE_DEFAULT: default seek method (flush only). + * @GST_CLAPPER_SEEK_MODE_ACCURATE: accurate seek method. + * @GST_CLAPPER_SEEK_MODE_FAST: fast seek method (next keyframe). + */ +typedef enum +{ + GST_CLAPPER_SEEK_MODE_DEFAULT, + GST_CLAPPER_SEEK_MODE_ACCURATE, + GST_CLAPPER_SEEK_MODE_FAST, +} GstClapperSeekMode; + /* ClapperError */ GST_CLAPPER_API GQuark gst_clapper_error_quark (void); @@ -149,6 +167,13 @@ void gst_clapper_stop (GstClapper *clapper GST_CLAPPER_API void gst_clapper_seek (GstClapper *clapper, GstClockTime position); +GST_CLAPPER_API +GstClapperSeekMode + gst_clapper_get_seek_mode (GstClapper *clapper); + +GST_CLAPPER_API +void gst_clapper_set_seek_mode (GstClapper *clapper, GstClapperSeekMode mode); + GST_CLAPPER_API void gst_clapper_set_rate (GstClapper *clapper, gdouble rate); @@ -285,12 +310,6 @@ void gst_clapper_config_set_position_update_interval (GstStructure *con GST_CLAPPER_API guint gst_clapper_config_get_position_update_interval (const GstStructure *config); -GST_CLAPPER_API -void gst_clapper_config_set_seek_accurate (GstStructure *config, gboolean accurate); - -GST_CLAPPER_API -gboolean gst_clapper_config_get_seek_accurate (const GstStructure *config); - GST_CLAPPER_API GstSample * gst_clapper_get_video_snapshot (GstClapper *clapper, GstClapperSnapshotFormat format, const GstStructure *config); From 3abfd2a5df1144d865eb6b6fedab4a8431131f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Wed, 27 Jan 2021 23:37:33 +0100 Subject: [PATCH 04/19] API: add TOC support (video chapters) --- .../clapper/gstclapper-media-info-private.h | 1 + lib/gst/clapper/gstclapper-media-info.c | 29 ++++++++---- lib/gst/clapper/gstclapper-media-info.h | 3 ++ lib/gst/clapper/gstclapper.c | 47 ++++++++++++++++++- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/lib/gst/clapper/gstclapper-media-info-private.h b/lib/gst/clapper/gstclapper-media-info-private.h index 00f148b3..3e420586 100644 --- a/lib/gst/clapper/gstclapper-media-info-private.h +++ b/lib/gst/clapper/gstclapper-media-info-private.h @@ -100,6 +100,7 @@ struct _GstClapperMediaInfo gchar *container; gboolean seekable, is_live; GstTagList *tags; + GstToc *toc; GstSample *image_sample; GList *stream_list; diff --git a/lib/gst/clapper/gstclapper-media-info.c b/lib/gst/clapper/gstclapper-media-info.c index 6a2ffe23..3f35215f 100644 --- a/lib/gst/clapper/gstclapper-media-info.c +++ b/lib/gst/clapper/gstclapper-media-info.c @@ -423,26 +423,21 @@ gst_clapper_media_info_finalize (GObject * object) GstClapperMediaInfo *info = GST_CLAPPER_MEDIA_INFO (object); g_free (info->uri); + g_free (info->title); + g_free (info->container); if (info->tags) gst_tag_list_unref (info->tags); - - g_free (info->title); - - g_free (info->container); - + if (info->toc) + gst_toc_unref (info->toc); if (info->image_sample) gst_sample_unref (info->image_sample); - if (info->audio_stream_list) g_list_free (info->audio_stream_list); - if (info->video_stream_list) g_list_free (info->video_stream_list); - if (info->subtitle_stream_list) g_list_free (info->subtitle_stream_list); - if (info->stream_list) g_list_free_full (info->stream_list, g_object_unref); @@ -567,6 +562,8 @@ gst_clapper_media_info_copy (GstClapperMediaInfo * ref) info->is_live = ref->is_live; if (ref->tags) info->tags = gst_tag_list_ref (ref->tags); + if (ref->toc) + info->toc = gst_toc_ref (ref->toc); if (ref->title) info->title = g_strdup (ref->title); if (ref->container) @@ -752,6 +749,20 @@ gst_clapper_media_info_get_tags (const GstClapperMediaInfo * info) return info->tags; } +/** + * gst_clapper_media_info_get_toc: + * @info: a #GstClapperMediaInfo + * + * Returns: (transfer none): the toc contained in media info. + */ +GstToc * +gst_clapper_media_info_get_toc (const GstClapperMediaInfo * info) +{ + g_return_val_if_fail (GST_IS_CLAPPER_MEDIA_INFO (info), NULL); + + return info->toc; +} + /** * gst_clapper_media_info_get_title: * @info: a #GstClapperMediaInfo diff --git a/lib/gst/clapper/gstclapper-media-info.h b/lib/gst/clapper/gstclapper-media-info.h index e926366b..1e101caf 100644 --- a/lib/gst/clapper/gstclapper-media-info.h +++ b/lib/gst/clapper/gstclapper-media-info.h @@ -233,6 +233,9 @@ guint gst_clapper_media_info_get_number_of_subtitle_streams (const GstCl GST_CLAPPER_API GstTagList * gst_clapper_media_info_get_tags (const GstClapperMediaInfo *info); +GST_CLAPPER_API +GstToc * gst_clapper_media_info_get_toc (const GstClapperMediaInfo *info); + GST_CLAPPER_API const gchar * gst_clapper_media_info_get_title (const GstClapperMediaInfo *info); diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 16d0ac14..2069abce 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -170,6 +170,7 @@ struct _GstClapper gint buffering; GstTagList *global_tags; + GstToc *global_toc; GstClapperMediaInfo *media_info; GstElement *current_vis_element; @@ -530,6 +531,8 @@ gst_clapper_finalize (GObject * object) g_free (self->subtitle_sid); if (self->global_tags) gst_tag_list_unref (self->global_tags); + if (self->global_toc) + gst_toc_unref (self->global_toc); if (self->video_renderer) g_object_unref (self->video_renderer); if (self->signal_dispatcher) @@ -1102,12 +1105,14 @@ emit_error (GstClapper * self, GError * err) g_object_unref (self->media_info); self->media_info = NULL; } - if (self->global_tags) { gst_tag_list_unref (self->global_tags); self->global_tags = NULL; } - + if (self->global_toc) { + gst_toc_unref (self->global_toc); + self->global_toc = NULL; + } self->seek_pending = FALSE; remove_seek_source (self); self->seek_position = GST_CLOCK_TIME_NONE; @@ -1807,6 +1812,37 @@ tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) gst_tag_list_unref (tags); } +static void +toc_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + GstToc *toc = NULL; + + gst_message_parse_toc (msg, &toc, NULL); + + GST_DEBUG_OBJECT (self, "received %s toc", + gst_toc_get_scope (toc) == GST_TOC_SCOPE_GLOBAL ? "global" : "stream"); + + if (gst_toc_get_scope (toc) == GST_TOC_SCOPE_GLOBAL) { + g_mutex_lock (&self->lock); + if (self->media_info) { + if (self->media_info->toc) + gst_toc_unref (self->media_info->toc); + self->media_info->toc = gst_toc_ref (toc); + media_info_update (self, self->media_info); + g_mutex_unlock (&self->lock); + emit_media_info_updated_signal (self); + } else { + if (self->global_toc) + gst_toc_unref (self->global_toc); + self->global_toc = gst_toc_ref (toc); + g_mutex_unlock (&self->lock); + } + } + + gst_toc_unref (toc); +} + static void element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) { @@ -2731,8 +2767,10 @@ gst_clapper_media_info_create (GstClapper * self) media_info = gst_clapper_media_info_new (self->uri); media_info->duration = gst_clapper_get_duration (self); media_info->tags = self->global_tags; + media_info->toc = self->global_toc; media_info->is_live = self->is_live; self->global_tags = NULL; + self->global_toc = NULL; query = gst_query_new_seeking (GST_FORMAT_TIME); if (gst_element_query (self->playbin, query)) @@ -2953,6 +2991,7 @@ gst_clapper_main (gpointer data) g_signal_connect (G_OBJECT (bus), "message::element", G_CALLBACK (element_cb), self); g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self); + g_signal_connect (G_OBJECT (bus), "message::toc", G_CALLBACK (toc_cb), self); if (self->use_playbin3) { g_signal_connect (G_OBJECT (bus), "message::stream-collection", @@ -3256,6 +3295,10 @@ gst_clapper_stop_internal (GstClapper * self, gboolean transient) gst_tag_list_unref (self->global_tags); self->global_tags = NULL; } + if (self->global_toc) { + gst_toc_unref (self->global_toc); + self->global_toc = NULL; + } self->seek_pending = FALSE; remove_seek_source (self); self->seek_position = GST_CLOCK_TIME_NONE; From 5785204c28edb180c20ca4833faf3cfef56945c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 08:32:49 +0100 Subject: [PATCH 05/19] API: prevent "notify::caps" from being reconnected on each start --- lib/gst/clapper/gstclapper.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 2069abce..731a5366 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -1621,8 +1621,6 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED && pending_state == GST_STATE_VOID_PENDING) { - GstElement *video_sink; - GstPad *video_sink_pad; gint64 duration = -1; GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled"); @@ -1633,20 +1631,6 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, self->media_info = gst_clapper_media_info_create (self); g_mutex_unlock (&self->lock); emit_media_info_updated_signal (self); - - g_object_get (self->playbin, "video-sink", &video_sink, NULL); - - if (video_sink) { - video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); - - if (video_sink_pad) { - g_signal_connect (video_sink_pad, "notify::caps", - (GCallback) notify_caps_cb, self); - gst_object_unref (video_sink_pad); - } - gst_object_unref (video_sink); - } - check_video_dimensions_changed (self); if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) { @@ -2953,11 +2937,16 @@ gst_clapper_main (gpointer data) if (self->video_renderer) { GstElement *video_sink = - gst_clapper_video_renderer_create_video_sink (self->video_renderer, - self); - - if (video_sink) + gst_clapper_video_renderer_create_video_sink (self->video_renderer, self); + if (video_sink) { + GstPad *video_sink_pad = gst_element_get_static_pad (video_sink, "sink"); + if (video_sink_pad) { + g_signal_connect (video_sink_pad, "notify::caps", + (GCallback) notify_caps_cb, self); + gst_object_unref (video_sink_pad); + } g_object_set (self->playbin, "video-sink", video_sink, NULL); + } } scaletempo = gst_element_factory_make ("scaletempo", NULL); From bbcba3ccc63de0dff05cde9311e61a261ee1ecc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 09:10:43 +0100 Subject: [PATCH 06/19] API: disable notify on props where it is unused Notify signal is a little problematic here as we already post a signal from player while jumping between APP and API contexts. Limit and disable it where not needed. --- lib/gst/clapper/gstclapper.c | 50 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 731a5366..4be444ef 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -328,49 +328,53 @@ gst_clapper_class_init (GstClapperClass * klass) g_param_spec_object ("video-renderer", "Video Renderer", "Video renderer to use for rendering videos", GST_TYPE_CLAPPER_VIDEO_RENDERER, - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_SIGNAL_DISPATCHER] = g_param_spec_object ("signal-dispatcher", "Signal Dispatcher", "Dispatcher for the signals to e.g. event loops", GST_TYPE_CLAPPER_SIGNAL_DISPATCHER, - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI", - DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + DEFAULT_URI, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI", - "Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + "Current Subtitle URI", NULL, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_POSITION] = g_param_spec_uint64 ("position", "Position", "Current Position", 0, G_MAXUINT64, DEFAULT_POSITION, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_MEDIA_INFO] = g_param_spec_object ("media-info", "Media Info", "Current media information", GST_TYPE_CLAPPER_MEDIA_INFO, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_CURRENT_AUDIO_TRACK] = g_param_spec_object ("current-audio-track", "Current Audio Track", "Current audio track information", GST_TYPE_CLAPPER_AUDIO_INFO, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_CURRENT_VIDEO_TRACK] = g_param_spec_object ("current-video-track", "Current Video Track", "Current video track information", GST_TYPE_CLAPPER_VIDEO_INFO, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_CURRENT_SUBTITLE_TRACK] = g_param_spec_object ("current-subtitle-track", "Current Subtitle Track", "Current audio subtitle information", GST_TYPE_CLAPPER_SUBTITLE_INFO, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_DURATION] = g_param_spec_uint64 ("duration", "Duration", "Duration", 0, G_MAXUINT64, DEFAULT_DURATION, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_VOLUME] = g_param_spec_double ("volume", "Volume", "Volume", @@ -378,12 +382,14 @@ gst_clapper_class_init (GstClapperClass * klass) param_specs[PROP_MUTE] = g_param_spec_boolean ("mute", "Mute", "Mute", - DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + DEFAULT_MUTE, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_PIPELINE] = g_param_spec_object ("pipeline", "Pipeline", "GStreamer pipeline that is used", - GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + GST_TYPE_ELEMENT, G_PARAM_READABLE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_RATE] = g_param_spec_double ("rate", "rate", "Playback rate", @@ -395,30 +401,32 @@ gst_clapper_class_init (GstClapperClass * klass) "Re-interpret a video stream as one of several frame-packed stereoscopic modes.", GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING, GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] = g_param_spec_flags ("video-multiview-flags", "Multiview Flags Override", "Override details of the multiview frame layout", GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_AUDIO_VIDEO_OFFSET] = g_param_spec_int64 ("audio-video-offset", "Audio Video Offset", "The synchronisation offset between audio and video in nanoseconds", - G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_SUBTITLE_VIDEO_OFFSET] = g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset", "The synchronisation offset between text and video in nanoseconds", - G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_SEEK_MODE] = g_param_spec_enum ("seek-mode", "Clapper Seek Mode", "Selected seek mode to use when performing seeks", - GST_TYPE_CLAPPER_SEEK_MODE, DEFAULT_SEEK_MODE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + GST_TYPE_CLAPPER_SEEK_MODE, DEFAULT_SEEK_MODE, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); @@ -940,8 +948,6 @@ position_updated_dispatch (gpointer user_data) if (data->clapper->target_state >= GST_STATE_PAUSED) { g_signal_emit (data->clapper, signals[SIGNAL_POSITION_UPDATED], 0, data->position); - g_object_notify_by_pspec (G_OBJECT (data->clapper), - param_specs[PROP_POSITION]); } } @@ -1510,8 +1516,6 @@ duration_changed_dispatch (gpointer user_data) if (data->clapper->target_state >= GST_STATE_PAUSED) { g_signal_emit (data->clapper, signals[SIGNAL_DURATION_CHANGED], 0, data->duration); - g_object_notify_by_pspec (G_OBJECT (data->clapper), - param_specs[PROP_DURATION]); } } @@ -2839,7 +2843,6 @@ volume_changed_dispatch (gpointer user_data) return; g_signal_emit (clapper, signals[SIGNAL_VOLUME_CHANGED], 0); - g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_VOLUME]); } static void @@ -2863,7 +2866,6 @@ mute_changed_dispatch (gpointer user_data) return; g_signal_emit (clapper, signals[SIGNAL_MUTE_CHANGED], 0); - g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_MUTE]); } static void From 90697d81a723bdd4f17e32b322de1d24e5a8aaf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 11:07:35 +0100 Subject: [PATCH 07/19] API: fix debug category init with bindings --- lib/gst/clapper/gstclapper.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 4be444ef..71d19ce3 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -272,6 +272,7 @@ static void remove_seek_source (GstClapper * self); static void gst_clapper_init (GstClapper * self) { + GST_DEBUG_CATEGORY_INIT (gst_clapper_debug, "Clapper", 0, "GstClapper"); GST_TRACE_OBJECT (self, "Initializing"); self = gst_clapper_get_instance_private (self); @@ -297,7 +298,7 @@ gst_clapper_init (GstClapper * self) } static void -config_quark_initialize (void) +quarks_initialize (void) { gint i; @@ -311,6 +312,8 @@ config_quark_initialize (void) _config_quark_table[i] = g_quark_from_static_string (_config_quark_strings[i]); } + + gst_clapper_error_quark (); } static void @@ -495,7 +498,7 @@ gst_clapper_class_init (GstClapperClass * klass) G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); - config_quark_initialize (); + quarks_initialize (); } static void @@ -3055,17 +3058,6 @@ gst_clapper_main (gpointer data) return NULL; } -static gpointer -gst_clapper_init_once (G_GNUC_UNUSED gpointer user_data) -{ - gst_init (NULL, NULL); - - GST_DEBUG_CATEGORY_INIT (gst_clapper_debug, "gst-clapper", 0, "GstClapper"); - gst_clapper_error_quark (); - - return NULL; -} - /** * gst_clapper_new: * @video_renderer: (transfer full) (allow-none): GstClapperVideoRenderer to use @@ -3085,15 +3077,11 @@ GstClapper * gst_clapper_new (GstClapperVideoRenderer * video_renderer, GstClapperSignalDispatcher * signal_dispatcher) { - static GOnce once = G_ONCE_INIT; GstClapper *self; - g_once (&once, gst_clapper_init_once, NULL); - self = g_object_new (GST_TYPE_CLAPPER, "video-renderer", video_renderer, "signal-dispatcher", signal_dispatcher, NULL); - gst_object_ref_sink (self); if (video_renderer) g_object_unref (video_renderer); From e731842b083beaf8b0aed4294604145b49cd2dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 13:06:25 +0100 Subject: [PATCH 08/19] API: remove "volume-changed" signal in favor of "notify::volume" We do not need both and notify is better here cause it allows binding volume scale value to the volume prop --- lib/gst/clapper/gstclapper.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 71d19ce3..375ac309 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -126,7 +126,6 @@ enum SIGNAL_WARNING, SIGNAL_VIDEO_DIMENSIONS_CHANGED, SIGNAL_MEDIA_INFO_UPDATED, - SIGNAL_VOLUME_CHANGED, SIGNAL_MUTE_CHANGED, SIGNAL_SEEK_DONE, SIGNAL_LAST @@ -166,6 +165,9 @@ struct _GstClapper gdouble rate; + /* Prevent unnecessary signals emissions */ + gdouble last_volume; + GstClapperState app_state; gint buffering; @@ -381,7 +383,8 @@ gst_clapper_class_init (GstClapperClass * klass) param_specs[PROP_VOLUME] = g_param_spec_double ("volume", "Volume", "Volume", - 0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + 0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_MUTE] = g_param_spec_boolean ("mute", "Mute", "Mute", @@ -396,7 +399,8 @@ gst_clapper_class_init (GstClapperClass * klass) param_specs[PROP_RATE] = g_param_spec_double ("rate", "rate", "Playback rate", - -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + -64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_VIDEO_MULTIVIEW_MODE] = g_param_spec_enum ("video-multiview-mode", @@ -478,11 +482,6 @@ gst_clapper_class_init (GstClapperClass * klass) G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_MEDIA_INFO); - signals[SIGNAL_VOLUME_CHANGED] = - g_signal_new ("volume-changed", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, - NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); - signals[SIGNAL_MUTE_CHANGED] = g_signal_new ("mute-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, @@ -2838,24 +2837,26 @@ subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index, } static void -volume_changed_dispatch (gpointer user_data) +volume_notify_dispatch (gpointer user_data) { GstClapper *clapper = user_data; if (clapper->inhibit_sigs) return; - g_signal_emit (clapper, signals[SIGNAL_VOLUME_CHANGED], 0); + g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_VOLUME]); } static void volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, GstClapper * self) { - if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, - signals[SIGNAL_VOLUME_CHANGED], 0, NULL, NULL, NULL) != 0) { + gdouble volume = gst_clapper_get_volume (self); + + if (self->last_volume != volume) { + self->last_volume = volume; gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, - volume_changed_dispatch, g_object_ref (self), + volume_notify_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref); } } From 15302a4b62960dc8e863a49b3971f2464c1797ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 14:30:37 +0100 Subject: [PATCH 09/19] API: use 1s update position interval by default --- lib/gst/clapper/gstclapper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 375ac309..71b47bc8 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -55,7 +55,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_clapper_debug); #define DEFAULT_VOLUME 1.0 #define DEFAULT_MUTE FALSE #define DEFAULT_RATE 1.0 -#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100 +#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 1000 #define DEFAULT_AUDIO_VIDEO_OFFSET 0 #define DEFAULT_SUBTITLE_VIDEO_OFFSET 0 #define DEFAULT_SEEK_MODE GST_CLAPPER_SEEK_MODE_DEFAULT From f2971371e107b7db041a5563261e9793b6c6d980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 14:55:16 +0100 Subject: [PATCH 10/19] API: remove clapper config structure Not useful anymore since the player does 1s interval by default and now supports changing seek mode without stopping playback (unlike config which worked only when stopped). --- lib/gst/clapper/gstclapper.c | 219 +---------------------------------- lib/gst/clapper/gstclapper.h | 19 --- 2 files changed, 1 insertion(+), 237 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 71b47bc8..0575bfc4 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -69,26 +69,6 @@ gst_clapper_error_quark (void) return g_quark_from_static_string ("gst-clapper-error-quark"); } -static GQuark QUARK_CONFIG; - -/* Keep ConfigQuarkId and _config_quark_strings ordered and synced */ -typedef enum -{ - CONFIG_QUARK_USER_AGENT = 0, - CONFIG_QUARK_POSITION_INTERVAL_UPDATE, - - CONFIG_QUARK_MAX -} ConfigQuarkId; - -static const gchar *_config_quark_strings[] = { - "user-agent", - "position-interval-update", -}; - -GQuark _config_quark_table[CONFIG_QUARK_MAX]; - -#define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q] - enum { PROP_0, @@ -177,8 +157,6 @@ struct _GstClapper GstElement *current_vis_element; - GstStructure *config; - GstClapperSeekMode seek_mode; /* Protected by lock */ @@ -284,13 +262,6 @@ gst_clapper_init (GstClapper * self) self->context = g_main_context_new (); self->loop = g_main_loop_new (self->context, FALSE); - - /* *INDENT-OFF* */ - self->config = gst_structure_new_id (QUARK_CONFIG, - CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS, - NULL); - /* *INDENT-ON* */ - self->seek_pending = FALSE; self->seek_position = GST_CLOCK_TIME_NONE; self->last_seek_time = GST_CLOCK_TIME_NONE; @@ -299,25 +270,6 @@ gst_clapper_init (GstClapper * self) GST_TRACE_OBJECT (self, "Initialized"); } -static void -quarks_initialize (void) -{ - gint i; - - QUARK_CONFIG = g_quark_from_static_string ("clapper-config"); - - if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX) - g_warning ("the quark table is not consistent! %d != %d", - (int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX); - - for (i = 0; i < CONFIG_QUARK_MAX; i++) { - _config_quark_table[i] = - g_quark_from_static_string (_config_quark_strings[i]); - } - - gst_clapper_error_quark (); -} - static void gst_clapper_class_init (GstClapperClass * klass) { @@ -496,8 +448,6 @@ gst_clapper_class_init (GstClapperClass * klass) g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); - - quarks_initialize (); } static void @@ -549,8 +499,6 @@ gst_clapper_finalize (GObject * object) g_object_unref (self->signal_dispatcher); if (self->current_vis_element) gst_object_unref (self->current_vis_element); - if (self->config) - gst_structure_free (self->config); if (self->collection) gst_object_unref (self->collection); g_mutex_clear (&self->lock); @@ -990,17 +938,10 @@ tick_cb (gpointer user_data) static void add_tick_source (GstClapper * self) { - guint position_update_interval_ms; - if (self->tick_source) return; - position_update_interval_ms = - gst_clapper_config_get_position_update_interval (self->config); - if (!position_update_interval_ms) - return; - - self->tick_source = g_timeout_source_new (position_update_interval_ms); + self->tick_source = g_timeout_source_new (DEFAULT_POSITION_UPDATE_INTERVAL_MS); g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL); g_source_attach (self->tick_source, self->context); } @@ -2887,21 +2828,7 @@ mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec, static void source_setup_cb (GstElement * playbin, GstElement * source, GstClapper * self) { - gchar *user_agent; - user_agent = gst_clapper_config_get_user_agent (self->config); - if (user_agent) { - GParamSpec *prop; - - prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source), - "user-agent"); - if (prop && prop->value_type == G_TYPE_STRING) { - GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent); - g_object_set (source, "user-agent", user_agent, NULL); - } - - g_free (user_agent); - } } static gpointer @@ -4548,150 +4475,6 @@ gst_clapper_error_get_name (GstClapperError error) return NULL; } -/** - * gst_clapper_set_config: - * @clapper: #GstClapper instance - * @config: (transfer full): a #GstStructure - * - * Set the configuration of the clapper. If the clapper is already configured, and - * the configuration haven't change, this function will return %TRUE. If the - * clapper is not in the GST_CLAPPER_STATE_STOPPED, this method will return %FALSE - * and active configuration will remain. - * - * @config is a #GstStructure that contains the configuration parameters for - * the clapper. - * - * This function takes ownership of @config. - * - * Returns: %TRUE when the configuration could be set. - */ -gboolean -gst_clapper_set_config (GstClapper * self, GstStructure * config) -{ - g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE); - g_return_val_if_fail (config != NULL, FALSE); - - g_mutex_lock (&self->lock); - - if (self->app_state != GST_CLAPPER_STATE_STOPPED) { - GST_INFO_OBJECT (self, "can't change config while clapper is %s", - gst_clapper_state_get_name (self->app_state)); - g_mutex_unlock (&self->lock); - return FALSE; - } - - if (self->config) - gst_structure_free (self->config); - self->config = config; - g_mutex_unlock (&self->lock); - - return TRUE; -} - -/** - * gst_clapper_get_config: - * @clapper: #GstClapper instance - * - * Get a copy of the current configuration of the clapper. This configuration - * can either be modified and used for the gst_clapper_set_config() call - * or it must be freed after usage. - * - * Returns: (transfer full): a copy of the current configuration of @clapper. Use - * gst_structure_free() after usage or gst_clapper_set_config(). - */ -GstStructure * -gst_clapper_get_config (GstClapper * self) -{ - GstStructure *ret; - - g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); - - g_mutex_lock (&self->lock); - ret = gst_structure_copy (self->config); - g_mutex_unlock (&self->lock); - - return ret; -} - -/** - * gst_clapper_config_set_user_agent: - * @config: a #GstClapper configuration - * @agent: the string to use as user agent - * - * Set the user agent to pass to the server if @clapper needs to connect - * to a server during playback. This is typically used when playing HTTP - * or RTSP streams. - */ -void -gst_clapper_config_set_user_agent (GstStructure * config, const gchar * agent) -{ - g_return_if_fail (config != NULL); - g_return_if_fail (agent != NULL); - - gst_structure_id_set (config, - CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL); -} - -/** - * gst_clapper_config_get_user_agent: - * @config: a #GstClapper configuration - * - * Return the user agent which has been configured using - * gst_clapper_config_set_user_agent() if any. - * - * Returns: (transfer full): the configured agent, or %NULL - */ -gchar * -gst_clapper_config_get_user_agent (const GstStructure * config) -{ - gchar *agent = NULL; - - g_return_val_if_fail (config != NULL, NULL); - - gst_structure_id_get (config, - CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL); - - return agent; -} - -/** - * gst_clapper_config_set_position_update_interval: - * @config: a #GstClapper configuration - * @interval: interval in ms - * - * set interval in milliseconds between two position-updated signals. - * pass 0 to stop updating the position. - */ -void -gst_clapper_config_set_position_update_interval (GstStructure * config, - guint interval) -{ - g_return_if_fail (config != NULL); - g_return_if_fail (interval <= 10000); - - gst_structure_id_set (config, - CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL); -} - -/** - * gst_clapper_config_get_position_update_interval: - * @config: a #GstClapper configuration - * - * Returns: current position update interval in milliseconds - */ -guint -gst_clapper_config_get_position_update_interval (const GstStructure * config) -{ - guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS; - - g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS); - - gst_structure_id_get (config, - CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL); - - return interval; -} - GType gst_clapper_seek_mode_get_type (void) { diff --git a/lib/gst/clapper/gstclapper.h b/lib/gst/clapper/gstclapper.h index 9ee6068e..173ad9f9 100644 --- a/lib/gst/clapper/gstclapper.h +++ b/lib/gst/clapper/gstclapper.h @@ -291,25 +291,6 @@ gint64 gst_clapper_get_subtitle_video_offset (GstClapper *clapper GST_CLAPPER_API void gst_clapper_set_subtitle_video_offset (GstClapper *clapper, gint64 offset); -GST_CLAPPER_API -gboolean gst_clapper_set_config (GstClapper *clapper, GstStructure *config); - -GST_CLAPPER_API -GstStructure * - gst_clapper_get_config (GstClapper *clapper); - -GST_CLAPPER_API -void gst_clapper_config_set_user_agent (GstStructure *config, const gchar *agent); - -GST_CLAPPER_API -gchar * gst_clapper_config_get_user_agent (const GstStructure *config); - -GST_CLAPPER_API -void gst_clapper_config_set_position_update_interval (GstStructure *config, guint interval); - -GST_CLAPPER_API -guint gst_clapper_config_get_position_update_interval (const GstStructure *config); - GST_CLAPPER_API GstSample * gst_clapper_get_video_snapshot (GstClapper *clapper, GstClapperSnapshotFormat format, const GstStructure *config); From f7a24b20c69f9ad7e87ddbc49642386bf9a1256a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 15:45:49 +0100 Subject: [PATCH 11/19] API: remove media info updated signal A signal telling that "something somewhere changed" that is emitted multiple times per second (when bitrate changes). Not useful at all and a disaster performance-wise. --- lib/gst/clapper/gstclapper.c | 91 ++---------------------------------- 1 file changed, 4 insertions(+), 87 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 0575bfc4..c8c2cf63 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -105,7 +105,6 @@ enum SIGNAL_ERROR, SIGNAL_WARNING, SIGNAL_VIDEO_DIMENSIONS_CHANGED, - SIGNAL_MEDIA_INFO_UPDATED, SIGNAL_MUTE_CHANGED, SIGNAL_SEEK_DONE, SIGNAL_LAST @@ -239,8 +238,6 @@ static GstClapperStreamInfo *gst_clapper_stream_info_get_current_from_stream_id static void stream_notify_cb (GstStreamCollection * collection, GstStream * stream, GParamSpec * pspec, GstClapper * self); -static void emit_media_info_updated_signal (GstClapper * self); - static void *get_title (GstTagList * tags); static void *get_container_format (GstTagList * tags); static void *get_from_tags (GstClapper * self, GstClapperMediaInfo * media_info, @@ -429,11 +426,6 @@ gst_clapper_class_init (GstClapperClass * klass) G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); - signals[SIGNAL_MEDIA_INFO_UPDATED] = - g_signal_new ("media-info-updated", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_MEDIA_INFO); - signals[SIGNAL_MUTE_CHANGED] = g_signal_new ("mute-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, @@ -1472,8 +1464,6 @@ duration_changed_signal_data_free (DurationChangedSignalData * data) static void emit_duration_changed (GstClapper * self, GstClockTime duration) { - gboolean updated = FALSE; - if (self->cached_duration == duration) return; @@ -1482,14 +1472,9 @@ emit_duration_changed (GstClapper * self, GstClockTime duration) self->cached_duration = duration; g_mutex_lock (&self->lock); - if (self->media_info) { + if (self->media_info) self->media_info->duration = duration; - updated = TRUE; - } g_mutex_unlock (&self->lock); - if (updated) { - emit_media_info_updated_signal (self); - } if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) { @@ -1577,7 +1562,6 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, g_object_unref (self->media_info); self->media_info = gst_clapper_media_info_create (self); g_mutex_unlock (&self->lock); - emit_media_info_updated_signal (self); check_video_dimensions_changed (self); if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) { @@ -1731,7 +1715,6 @@ tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) self->media_info->tags = gst_tag_list_ref (tags); media_info_update (self, self->media_info); g_mutex_unlock (&self->lock); - emit_media_info_updated_signal (self); } else { if (self->global_tags) gst_tag_list_unref (self->global_tags); @@ -1762,7 +1745,6 @@ toc_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) self->media_info->toc = gst_toc_ref (toc); media_info_update (self, self->media_info); g_mutex_unlock (&self->lock); - emit_media_info_updated_signal (self); } else { if (self->global_toc) gst_toc_unref (self->global_toc); @@ -1862,7 +1844,6 @@ stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, { GstClapper *self = GST_CLAPPER (user_data); GstStreamCollection *collection = NULL; - gboolean updated = FALSE; gst_message_parse_stream_collection (msg, &collection); @@ -1870,12 +1851,9 @@ stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, return; g_mutex_lock (&self->lock); - updated = update_stream_collection (self, collection); + update_stream_collection (self, collection); gst_object_unref (collection); g_mutex_unlock (&self->lock); - - if (self->media_info && updated) - emit_media_info_updated_signal (self); } static void @@ -1884,7 +1862,6 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, { GstClapper *self = GST_CLAPPER (user_data); GstStreamCollection *collection = NULL; - gboolean updated = FALSE; guint i, len; gst_message_parse_streams_selected (msg, &collection); @@ -1893,7 +1870,7 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, return; g_mutex_lock (&self->lock); - updated = update_stream_collection (self, collection); + update_stream_collection (self, collection); gst_object_unref (collection); g_free (self->video_sid); @@ -1934,9 +1911,6 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, *current_sid = g_strdup (stream_id); } g_mutex_unlock (&self->lock); - - if (self->media_info && updated) - emit_media_info_updated_signal (self); } static void @@ -1963,55 +1937,6 @@ clapper_clear_flag (GstClapper * self, gint pos) GST_DEBUG_OBJECT (self, "setting flags=%#x", flags); } -typedef struct -{ - GstClapper *clapper; - GstClapperMediaInfo *info; -} MediaInfoUpdatedSignalData; - -static void -media_info_updated_dispatch (gpointer user_data) -{ - MediaInfoUpdatedSignalData *data = user_data; - - if (data->clapper->inhibit_sigs) - return; - - if (data->clapper->target_state >= GST_STATE_PAUSED) { - g_signal_emit (data->clapper, signals[SIGNAL_MEDIA_INFO_UPDATED], 0, - data->info); - } -} - -static void -free_media_info_updated_signal_data (MediaInfoUpdatedSignalData * data) -{ - g_object_unref (data->clapper); - g_object_unref (data->info); - g_free (data); -} - -/* - * emit_media_info_updated_signal: - * - * create a new copy of self->media_info object and emits the newly created - * copy to user application. The newly created media_info will be unref'ed - * as part of signal finalize method. - */ -static void -emit_media_info_updated_signal (GstClapper * self) -{ - MediaInfoUpdatedSignalData *data = g_new (MediaInfoUpdatedSignalData, 1); - data->clapper = g_object_ref (self); - g_mutex_lock (&self->lock); - data->info = gst_clapper_media_info_copy (self->media_info); - g_mutex_unlock (&self->lock); - - gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, - media_info_updated_dispatch, data, - (GDestroyNotify) free_media_info_updated_signal_data); -} - static GstCaps * get_caps (GstClapper * self, gint stream_index, GType type) { @@ -2346,7 +2271,6 @@ stream_notify_cb (GstStreamCollection * collection, GstStream * stream, { GstClapperStreamInfo *info; const gchar *stream_id; - gboolean emit_signal = FALSE; if (!self->media_info) return; @@ -2359,14 +2283,9 @@ stream_notify_cb (GstStreamCollection * collection, GstStream * stream, g_mutex_lock (&self->lock); info = gst_clapper_stream_info_find_from_stream_id (self->media_info, stream_id); - if (info) { + if (info) gst_clapper_stream_info_update_from_stream (self, info, stream); - emit_signal = TRUE; - } g_mutex_unlock (&self->lock); - - if (emit_signal) - emit_media_info_updated_signal (self); } static void @@ -2749,8 +2668,6 @@ tags_changed_cb (GstClapper * self, gint stream_index, GType type) s = gst_clapper_stream_info_find (self->media_info, type, stream_index); gst_clapper_stream_info_update_tags_and_caps (self, s); g_mutex_unlock (&self->lock); - - emit_media_info_updated_signal (self); } static void From dea77cc39f848664f0bee7212613ae45292ceddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 17:39:52 +0100 Subject: [PATCH 12/19] API: notify about speed value reset on STOP --- lib/gst/clapper/gstclapper.c | 25 ++++++++++++++++++++++++- src/player.js | 5 ----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index c8c2cf63..47d7a74f 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -850,6 +850,25 @@ state_changed_signal_data_free (StateChangedSignalData * data) g_free (data); } +static void +rate_notify_dispatch (gpointer user_data) +{ + GstClapper *clapper = user_data; + + if (clapper->inhibit_sigs) + return; + + g_object_notify_by_pspec (G_OBJECT (clapper), param_specs[PROP_RATE]); +} + +static void +emit_rate_notify (GstClapper * self) +{ + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + rate_notify_dispatch, g_object_ref (self), + (GDestroyNotify) g_object_unref); +} + static void change_state (GstClapper * self, GstClapperState state) { @@ -861,6 +880,11 @@ change_state (GstClapper * self, GstClapperState state) gst_clapper_state_get_name (state)); self->app_state = state; + if (state == GST_CLAPPER_STATE_STOPPED && self->rate != 1.0) { + self->rate = 1.0; + emit_rate_notify (self); + } + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, signals[SIGNAL_STATE_CHANGED], 0, NULL, NULL, NULL) != 0) { StateChangedSignalData *data = g_new (StateChangedSignalData, 1); @@ -3127,7 +3151,6 @@ gst_clapper_stop_internal (GstClapper * self, gboolean transient) remove_seek_source (self); self->seek_position = GST_CLOCK_TIME_NONE; self->last_seek_time = GST_CLOCK_TIME_NONE; - self->rate = 1.0; if (self->collection) { if (self->stream_notify_id) g_signal_handler_disconnect (self->collection, self->stream_notify_id); diff --git a/src/player.js b/src/player.js index 21f31bd0..9f8e85d2 100644 --- a/src/player.js +++ b/src/player.js @@ -78,11 +78,6 @@ class ClapperPlayer extends PlayerBase set_uri(uri) { - /* FIXME: Player does not notify about - * rate change after file load */ - if(this.rate !== 1) - this.set_rate(1); - if(Gst.Uri.get_protocol(uri) !== 'file') return super.set_uri(uri); From fcf942689284652478b30cbe92c08ffa3f828fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 18:26:37 +0100 Subject: [PATCH 13/19] API: remove unused seek-done signal --- lib/gst/clapper/gstclapper.c | 45 ------------------------------------ 1 file changed, 45 deletions(-) diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index 47d7a74f..07b341ad 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -106,7 +106,6 @@ enum SIGNAL_WARNING, SIGNAL_VIDEO_DIMENSIONS_CHANGED, SIGNAL_MUTE_CHANGED, - SIGNAL_SEEK_DONE, SIGNAL_LAST }; @@ -435,11 +434,6 @@ gst_clapper_class_init (GstClapperClass * klass) g_signal_new ("warning", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR); - - signals[SIGNAL_SEEK_DONE] = - g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, - NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); } static void @@ -1512,44 +1506,6 @@ emit_duration_changed (GstClapper * self, GstClockTime duration) } } -typedef struct -{ - GstClapper *clapper; - GstClockTime position; -} SeekDoneSignalData; - -static void -seek_done_dispatch (gpointer user_data) -{ - SeekDoneSignalData *data = user_data; - - if (data->clapper->inhibit_sigs) - return; - - g_signal_emit (data->clapper, signals[SIGNAL_SEEK_DONE], 0, data->position); -} - -static void -seek_done_signal_data_free (SeekDoneSignalData * data) -{ - g_object_unref (data->clapper); - g_free (data); -} - -static void -emit_seek_done (GstClapper * self) -{ - if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, - signals[SIGNAL_SEEK_DONE], 0, NULL, NULL, NULL) != 0) { - SeekDoneSignalData *data = g_new (SeekDoneSignalData, 1); - - data->clapper = g_object_ref (self); - data->position = gst_clapper_get_position (self); - gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, - seek_done_dispatch, data, (GDestroyNotify) seek_done_signal_data_free); - } -} - static void state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) @@ -1613,7 +1569,6 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gst_clapper_seek_internal_locked (self); } else { GST_DEBUG_OBJECT (self, "Seek finished"); - emit_seek_done (self); } } From 4ad2b707dde50c47c9400fb0cd11232663ded72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 28 Jan 2021 19:09:53 +0100 Subject: [PATCH 14/19] Remove player config options that were changed/fixed in API --- src/playerBase.js | 54 ++++------------------------------------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/src/playerBase.js b/src/playerBase.js index 048fac3b..d3db2478 100644 --- a/src/playerBase.js +++ b/src/playerBase.js @@ -30,13 +30,7 @@ class ClapperPlayerBase extends GstClapper.Clapper const glsinkbin = Gst.ElementFactory.make('glsinkbin', null); glsinkbin.sink = gtk4glsink; - const context = GLib.MainContext.ref_thread_default(); - const acquired = context.acquire(); - debug(`default context acquired: ${acquired}`); - - const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher({ - application_context: context, - }); + const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher(); const renderer = new GstClapper.ClapperVideoOverlayVideoRenderer({ video_sink: glsinkbin }); @@ -62,10 +56,6 @@ class ClapperPlayerBase extends GstClapper.Clapper this.set_and_bind_settings(); settings.connect('changed', this._onSettingsKeyChanged.bind(this)); - - /* FIXME: additional reference for working around GstPlayer - * buggy signal dispatcher on self. Remove when ported to BUS API */ - this.ref(); } set_and_bind_settings() @@ -87,14 +77,6 @@ class ClapperPlayerBase extends GstClapper.Clapper set_initial_config() { - const gstClapperConfig = { - position_update_interval: 1000, - user_agent: 'clapper', - }; - - for(let option of Object.keys(gstClapperConfig)) - this.set_config_option(option, gstClapperConfig[option]); - this.set_mute(false); /* FIXME: change into option in preferences */ @@ -102,20 +84,6 @@ class ClapperPlayerBase extends GstClapper.Clapper pipeline.ring_buffer_max_size = 8 * 1024 * 1024; } - set_config_option(option, value) - { - const setOption = GstClapper.Clapper[`config_set_${option}`]; - if(!setOption) - return debug(`unsupported option: ${option}`, 'LEVEL_WARNING'); - - const config = this.get_config(); - setOption(config, value); - const success = this.set_config(config); - - if(!success) - debug(`could not change option: ${option}`); - } - set_all_plugins_ranks() { let data = []; @@ -181,30 +149,16 @@ class ClapperPlayerBase extends GstClapper.Clapper switch(key) { case 'seeking-mode': - const isSeekMode = (typeof this.set_seek_mode !== 'undefined'); this.seekingMode = settings.get_string('seeking-mode'); switch(this.seekingMode) { case 'fast': - if(isSeekMode) - this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); - else - this.set_config_option('seek_fast', true); + this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); break; case 'accurate': - if(isSeekMode) - this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE); - else { - this.set_config_option('seek_fast', false); - this.set_config_option('seek_accurate', true); - } + this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE); break; default: - if(isSeekMode) - this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); - else { - this.set_config_option('seek_fast', false); - this.set_config_option('seek_accurate', false); - } + this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); break; } break; From 2ce44d4e63907e66122c4ce9c66b856a44d26d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Fri, 29 Jan 2021 17:27:39 +0100 Subject: [PATCH 15/19] Combine GStreamer GTK4 plugin with API Ship custom gtk4glsink plugin as part of API insead of normal gstreamer plugin. This avoids gstreamer plugin registry conflicts with gtk3 plugin and allows more customization. --- lib/gst/clapper/clapper.h | 1 + lib/gst/clapper/gstclapper-gtk4-plugin.c | 145 ++++++ lib/gst/clapper/gstclapper-gtk4-plugin.h | 86 ++++ lib/gst/clapper/gtk4/gstgtkbasesink.c | 556 +++++++++++++++++++++ lib/gst/clapper/gtk4/gstgtkbasesink.h | 96 ++++ lib/gst/clapper/gtk4/gstgtkglsink.c | 387 +++++++++++++++ lib/gst/clapper/gtk4/gstgtkglsink.h | 65 +++ lib/gst/clapper/gtk4/gstgtkutils.c | 71 +++ lib/gst/clapper/gtk4/gstgtkutils.h | 29 ++ lib/gst/clapper/gtk4/gtkconfig.h | 31 ++ lib/gst/clapper/gtk4/gtkgstbasewidget.c | 605 +++++++++++++++++++++++ lib/gst/clapper/gtk4/gtkgstbasewidget.h | 101 ++++ lib/gst/clapper/gtk4/gtkgstglwidget.c | 592 ++++++++++++++++++++++ lib/gst/clapper/gtk4/gtkgstglwidget.h | 77 +++ lib/gst/clapper/meson.build | 55 ++- lib/meson.build | 79 ++- src/playerBase.js | 18 +- 17 files changed, 2964 insertions(+), 30 deletions(-) create mode 100644 lib/gst/clapper/gstclapper-gtk4-plugin.c create mode 100644 lib/gst/clapper/gstclapper-gtk4-plugin.h create mode 100644 lib/gst/clapper/gtk4/gstgtkbasesink.c create mode 100644 lib/gst/clapper/gtk4/gstgtkbasesink.h create mode 100644 lib/gst/clapper/gtk4/gstgtkglsink.c create mode 100644 lib/gst/clapper/gtk4/gstgtkglsink.h create mode 100644 lib/gst/clapper/gtk4/gstgtkutils.c create mode 100644 lib/gst/clapper/gtk4/gstgtkutils.h create mode 100644 lib/gst/clapper/gtk4/gtkconfig.h create mode 100644 lib/gst/clapper/gtk4/gtkgstbasewidget.c create mode 100644 lib/gst/clapper/gtk4/gtkgstbasewidget.h create mode 100644 lib/gst/clapper/gtk4/gtkgstglwidget.c create mode 100644 lib/gst/clapper/gtk4/gtkgstglwidget.h diff --git a/lib/gst/clapper/clapper.h b/lib/gst/clapper/clapper.h index d81739f4..eb8c483c 100644 --- a/lib/gst/clapper/clapper.h +++ b/lib/gst/clapper/clapper.h @@ -28,5 +28,6 @@ #include #include #include +#include #endif /* __CLAPPER_H__ */ diff --git a/lib/gst/clapper/gstclapper-gtk4-plugin.c b/lib/gst/clapper/gstclapper-gtk4-plugin.c new file mode 100644 index 00000000..5a38378f --- /dev/null +++ b/lib/gst/clapper/gstclapper-gtk4-plugin.c @@ -0,0 +1,145 @@ +/* GStreamer + * + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-gtk4plugin + * @title: GstClapperGtk4Plugin + * @short_description: Clapper GTK4 plugin + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-gtk4-plugin.h" +#include "gtk4/gstgtkglsink.h" + +enum +{ + PROP_0, + PROP_VIDEO_SINK, + PROP_LAST +}; + +#define parent_class gst_clapper_gtk4_plugin_parent_class +G_DEFINE_TYPE_WITH_CODE (GstClapperGtk4Plugin, gst_clapper_gtk4_plugin, + G_TYPE_OBJECT, NULL); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_clapper_gtk4_plugin_constructed (GObject * object); +static void gst_clapper_gtk4_plugin_finalize (GObject * object); +static void gst_clapper_gtk4_plugin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void gst_clapper_gtk4_plugin_init + (G_GNUC_UNUSED GstClapperGtk4Plugin * self) +{ +} + +static void gst_clapper_gtk4_plugin_class_init + (G_GNUC_UNUSED GstClapperGtk4PluginClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = gst_clapper_gtk4_plugin_constructed; + gobject_class->get_property = gst_clapper_gtk4_plugin_get_property; + gobject_class->finalize = gst_clapper_gtk4_plugin_finalize; + + param_specs[PROP_VIDEO_SINK] = + g_param_spec_object ("video-sink", + "Video Sink", "Video sink to use with video renderer", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} + +static void +gst_clapper_gtk4_plugin_constructed (GObject * object) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + if (!self->video_sink) + self->video_sink = g_object_new (GST_TYPE_GTK_GL_SINK, NULL); + + gst_object_ref_sink (self->video_sink); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_clapper_gtk4_plugin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + g_value_set_object (value, self->video_sink); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_gtk4_plugin_finalize (GObject * object) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + gst_object_unref (self->video_sink); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +#define C_ENUM(v) ((gint) v) + +GType +gst_clapper_gtk4_plugin_type_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA), "GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA", "glarea"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperGtk4PluginType", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_clapper_gtk4_plugin_new: + * @plugin_type: (allow-none): Requested GstClapperGtk4PluginType + * + * Creates a new GTK4 plugin. + * + * Returns: (transfer full): the new GstClapperGtk4Plugin + */ +GstClapperGtk4Plugin * +gst_clapper_gtk4_plugin_new (G_GNUC_UNUSED const GstClapperGtk4PluginType plugin_type) +{ + return g_object_new (GST_TYPE_CLAPPER_GTK4_PLUGIN, NULL); +} diff --git a/lib/gst/clapper/gstclapper-gtk4-plugin.h b/lib/gst/clapper/gstclapper-gtk4-plugin.h new file mode 100644 index 00000000..a729d9f6 --- /dev/null +++ b/lib/gst/clapper/gstclapper-gtk4-plugin.h @@ -0,0 +1,86 @@ +/* GStreamer + * + * Copyright (C) 2021 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_GTK4_PLUGIN_H__ +#define __GST_CLAPPER_GTK4_PLUGIN_H__ + +#include +#include + +G_BEGIN_DECLS + +/* PluginType */ +GST_CLAPPER_API +GType gst_clapper_gtk4_plugin_type_get_type (void); +#define GST_TYPE_CLAPPER_GTK4_PLUGIN_TYPE (gst_clapper_gtk4_plugin_type_get_type ()) + +/** + * GstClapperGtk4PluginType: + * @GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA: GTK4 GLArea sink. + */ +typedef enum +{ + GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA, +} GstClapperGtk4PluginType; + +#define GST_TYPE_CLAPPER_GTK4_PLUGIN (gst_clapper_gtk4_plugin_get_type ()) +#define GST_IS_CLAPPER_GTK4_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN)) +#define GST_IS_CLAPPER_GTK4_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_GTK4_PLUGIN)) +#define GST_CLAPPER_GTK4_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4PluginClass)) +#define GST_CLAPPER_GTK4_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4Plugin)) +#define GST_CLAPPER_GTK4_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4PluginClass)) +#define GST_CLAPPER_GTK4_PLUGIN_CAST(obj) ((GstClapperGtk4Plugin*)(obj)) + +typedef struct _GstClapperGtk4Plugin GstClapperGtk4Plugin; +typedef struct _GstClapperGtk4PluginClass GstClapperGtk4PluginClass; + +/** + * GstClapperGtk4Plugin: + * + * Opaque #GstClapperGtk4Plugin object + */ +struct _GstClapperGtk4Plugin +{ + /* */ + GObject parent; + + GstElement *video_sink; +}; + +/** + * GstClapperGtk4PluginClass: + * + * The #GstClapperGtk4PluginClass struct only contains private data + */ +struct _GstClapperGtk4PluginClass +{ + /* */ + GstElementClass parent_class; +}; + +GST_CLAPPER_API +GType gst_clapper_gtk4_plugin_get_type (void); + +GST_CLAPPER_API +GstClapperGtk4Plugin * gst_clapper_gtk4_plugin_new (const GstClapperGtk4PluginType plugin_type); + +G_END_DECLS + +#endif /* __GST_CLAPPER_GTK4_PLUGIN__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.c b/lib/gst/clapper/gtk4/gstgtkbasesink.c new file mode 100644 index 00000000..1ebdc81b --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.c @@ -0,0 +1,556 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gtkgstsink + * @title: GstGtkBaseSink + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstgtkbasesink.h" +#include "gstgtkutils.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink); +#define GST_CAT_DEFAULT gst_debug_gtk_base_sink + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 +#define DEFAULT_IGNORE_ALPHA TRUE + +static void gst_gtk_base_sink_finalize (GObject * object); +static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * param_spec); +static void gst_gtk_base_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * param_spec); + +static gboolean gst_gtk_base_sink_start (GstBaseSink * bsink); +static gboolean gst_gtk_base_sink_stop (GstBaseSink * bsink); + +static GstStateChangeReturn +gst_gtk_base_sink_change_state (GstElement * element, + GstStateChange transition); + +static void gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end); +static gboolean gst_gtk_base_sink_set_caps (GstBaseSink * bsink, + GstCaps * caps); +static GstFlowReturn gst_gtk_base_sink_show_frame (GstVideoSink * bsink, + GstBuffer * buf); + +static void +gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface); + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_IGNORE_ALPHA, +}; + +#define gst_gtk_base_sink_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink, + GST_TYPE_VIDEO_SINK, + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_gtk_base_sink_navigation_interface_init); + GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink, + "gtkbasesink", 0, "GTK Video Sink base class")); + + +static void +gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstVideoSinkClass *gstvideosink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstvideosink_class = (GstVideoSinkClass *) klass; + + gobject_class->set_property = gst_gtk_base_sink_set_property; + gobject_class->get_property = gst_gtk_base_sink_get_property; + + g_object_class_install_property (gobject_class, PROP_WIDGET, + g_param_spec_object ("widget", "GTK Widget", + "The GtkWidget to place in the widget hierarchy " + "(must only be get from the GTK main thread)", + GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + DEFAULT_FORCE_ASPECT_RATIO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO, + gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, + G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Disabling alpha was removed in GTK4 */ +#if !defined(BUILD_FOR_GTK4) + g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA, + g_param_spec_boolean ("ignore-alpha", "Ignore Alpha", + "When enabled, alpha will be ignored and converted to black", + DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif + + gobject_class->finalize = gst_gtk_base_sink_finalize; + + gstelement_class->change_state = gst_gtk_base_sink_change_state; + gstbasesink_class->set_caps = gst_gtk_base_sink_set_caps; + gstbasesink_class->get_times = gst_gtk_base_sink_get_times; + gstbasesink_class->start = gst_gtk_base_sink_start; + gstbasesink_class->stop = gst_gtk_base_sink_stop; + + gstvideosink_class->show_frame = gst_gtk_base_sink_show_frame; + + gst_type_mark_as_plugin_api (GST_TYPE_GTK_BASE_SINK, 0); +} + +static void +gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink) +{ + gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + gtk_sink->par_n = DEFAULT_PAR_N; + gtk_sink->par_d = DEFAULT_PAR_D; + gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA; +} + +static void +gst_gtk_base_sink_finalize (GObject * object) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + GST_DEBUG ("finalizing base sink"); + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->window && gtk_sink->window_destroy_id) + g_signal_handler_disconnect (gtk_sink->window, gtk_sink->window_destroy_id); + if (gtk_sink->widget && gtk_sink->widget_destroy_id) + g_signal_handler_disconnect (gtk_sink->widget, gtk_sink->widget_destroy_id); + + g_clear_object (>k_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +widget_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink) +{ + GST_OBJECT_LOCK (gtk_sink); + g_clear_object (>k_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); +} + +static void +window_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink) +{ + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget) { + if (gtk_sink->widget_destroy_id) { + g_signal_handler_disconnect (gtk_sink->widget, + gtk_sink->widget_destroy_id); + gtk_sink->widget_destroy_id = 0; + } + g_clear_object (>k_sink->widget); + } + gtk_sink->window = NULL; + GST_OBJECT_UNLOCK (gtk_sink); +} + +static GtkGstBaseWidget * +gst_gtk_base_sink_get_widget (GstGtkBaseSink * gtk_sink) +{ + if (gtk_sink->widget != NULL) + return gtk_sink->widget; + + /* Ensure GTK is initialized, this has no side effect if it was already + * initialized. Also, we do that lazily, so the application can be first */ + if (!gtk_init_check ( +#if !defined(BUILD_FOR_GTK4) + NULL, NULL +#endif + )) { + GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization."); + return NULL; + } + + g_assert (GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget); + gtk_sink->widget = (GtkGstBaseWidget *) + GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget (); + + gtk_sink->bind_aspect_ratio = + g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget, + "force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + gtk_sink->bind_pixel_aspect_ratio = + g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget, + "pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); +#if !defined(BUILD_FOR_GTK4) + gtk_sink->bind_ignore_alpha = + g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget, + "ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); +#endif + + /* Take the floating ref, other wise the destruction of the container will + * make this widget disappear possibly before we are done. */ + gst_object_ref_sink (gtk_sink->widget); + + gtk_sink->widget_destroy_id = g_signal_connect (gtk_sink->widget, "destroy", + G_CALLBACK (widget_destroy_cb), gtk_sink); + + /* back pointer */ + gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (gtk_sink->widget), + GST_ELEMENT (gtk_sink)); + + return gtk_sink->widget; +} + +static void +gst_gtk_base_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + switch (prop_id) { + case PROP_WIDGET: + { + GObject *widget = NULL; + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget != NULL) + widget = G_OBJECT (gtk_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); + + if (!widget) + widget = + gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget, + gtk_sink); + + g_value_set_object (value, widget); + break; + } + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, gtk_sink->force_aspect_ratio); + break; + case PROP_PIXEL_ASPECT_RATIO: + gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d); + break; + case PROP_IGNORE_ALPHA: + g_value_set_boolean (value, gtk_sink->ignore_alpha); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_gtk_base_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + gtk_sink->force_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_PIXEL_ASPECT_RATIO: + gtk_sink->par_n = gst_value_get_fraction_numerator (value); + gtk_sink->par_d = gst_value_get_fraction_denominator (value); + break; + case PROP_IGNORE_ALPHA: + gtk_sink->ignore_alpha = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_gtk_base_sink_navigation_send_event (GstNavigation * navigation, + GstStructure * structure) +{ + GstGtkBaseSink *sink = GST_GTK_BASE_SINK (navigation); + GstEvent *event; + GstPad *pad; + + event = gst_event_new_navigation (structure); + pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)); + + GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure); + + if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) { + if (!gst_pad_send_event (pad, gst_event_ref (event))) { + /* If upstream didn't handle the event we'll post a message with it + * for the application in case it wants to do something with it */ + gst_element_post_message (GST_ELEMENT_CAST (sink), + gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event)); + } + gst_event_unref (event); + gst_object_unref (pad); + } +} + +static void +gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_gtk_base_sink_navigation_send_event; +} + +static gboolean +gst_gtk_base_sink_start_on_main (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink); + GtkWidget *toplevel; +#if defined(BUILD_FOR_GTK4) + GtkRoot *root; +#endif + + if (gst_gtk_base_sink_get_widget (gst_sink) == NULL) + return FALSE; + + /* After this point, gtk_sink->widget will always be set */ + +#if defined(BUILD_FOR_GTK4) + root = gtk_widget_get_root (GTK_WIDGET (gst_sink->widget)); + if (!GTK_IS_ROOT (root)) { + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (gst_sink->widget)); + if (parent) { + GtkWidget *temp_parent; + while ((temp_parent = gtk_widget_get_parent (parent))) + parent = temp_parent; + } + toplevel = (parent) ? parent : GTK_WIDGET (gst_sink->widget); +#else + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_sink->widget)); + if (!gtk_widget_is_toplevel (toplevel)) { +#endif + /* sanity check */ + g_assert (klass->window_title); + + /* User did not add widget its own UI, let's popup a new GtkWindow to + * make gst-launch-1.0 work. */ + gst_sink->window = gtk_window_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WINDOW_TOPLEVEL +#endif + ); + gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480); + gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title); +#if defined(BUILD_FOR_GTK4) + gtk_window_set_child (GTK_WINDOW ( +#else + gtk_container_add (GTK_CONTAINER ( +#endif + gst_sink->window), toplevel); + + gst_sink->window_destroy_id = g_signal_connect ( +#if defined(BUILD_FOR_GTK4) + GTK_WINDOW (gst_sink->window), +#else + gst_sink->window, +#endif + "destroy", G_CALLBACK (window_destroy_cb), gst_sink); + } + + return TRUE; +} + +static gboolean +gst_gtk_base_sink_start (GstBaseSink * bsink) +{ + return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_base_sink_start_on_main, bsink); +} + +static gboolean +gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + + if (gst_sink->window) { +#if defined(BUILD_FOR_GTK4) + gtk_window_destroy (GTK_WINDOW (gst_sink->window)); +#else + gtk_widget_destroy (gst_sink->window); +#endif + gst_sink->window = NULL; + gst_sink->widget = NULL; + } + + return TRUE; +} + +static gboolean +gst_gtk_base_sink_stop (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + + if (gst_sink->window) + return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_base_sink_stop_on_main, bsink); + + return TRUE; +} + +static void +gst_gtk_window_show_all_and_unref (GtkWidget * window) +{ +#if defined(BUILD_FOR_GTK4) + gtk_window_present (GTK_WINDOW (window)); +#else + gtk_widget_show_all (window); +#endif + g_object_unref (window); +} + +static GstStateChangeReturn +gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (element, "changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + GtkWindow *window = NULL; + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->window) + window = g_object_ref (GTK_WINDOW (gtk_sink->window)); + GST_OBJECT_UNLOCK (gtk_sink); + + if (window) { + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_window_show_all_and_unref, window); + } + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget) + gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL); + GST_OBJECT_UNLOCK (gtk_sink); + break; + default: + break; + } + + return ret; +} + +static void +gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end) +{ + GstGtkBaseSink *gtk_sink; + + gtk_sink = GST_GTK_BASE_SINK (bsink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + *start = GST_BUFFER_TIMESTAMP (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) + *end = *start + GST_BUFFER_DURATION (buf); + else { + if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) { + *end = *start + + gst_util_uint64_scale_int (GST_SECOND, + GST_VIDEO_INFO_FPS_D (>k_sink->v_info), + GST_VIDEO_INFO_FPS_N (>k_sink->v_info)); + } + } + } +} + +gboolean +gst_gtk_base_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (bsink); + + GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps); + + if (!gst_video_info_from_caps (>k_sink->v_info, caps)) + return FALSE; + + GST_OBJECT_LOCK (gtk_sink); + + if (gtk_sink->widget == NULL) { + GST_OBJECT_UNLOCK (gtk_sink); + GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return FALSE; + } + + if (!gtk_gst_base_widget_set_format (gtk_sink->widget, >k_sink->v_info)) { + GST_OBJECT_UNLOCK (gtk_sink); + return FALSE; + } + GST_OBJECT_UNLOCK (gtk_sink); + + return TRUE; +} + +static GstFlowReturn +gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf) +{ + GstGtkBaseSink *gtk_sink; + + GST_TRACE ("rendering buffer:%p", buf); + + gtk_sink = GST_GTK_BASE_SINK (vsink); + + GST_OBJECT_LOCK (vsink); + + if (gtk_sink->widget == NULL) { + GST_OBJECT_UNLOCK (gtk_sink); + GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return GST_FLOW_ERROR; + } + + gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf); + + GST_OBJECT_UNLOCK (gtk_sink); + + return GST_FLOW_OK; +} diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.h b/lib/gst/clapper/gtk4/gstgtkbasesink.h new file mode 100644 index 00000000..db0acb2c --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.h @@ -0,0 +1,96 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_BASE_SINK_H__ +#define __GST_GTK_BASE_SINK_H__ + +#include +#include +#include +#include + +#include "gtkgstbasewidget.h" + +#define GST_TYPE_GTK_BASE_SINK (gst_gtk_base_sink_get_type()) +#define GST_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSink)) +#define GST_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSinkClass)) +#define GST_GTK_BASE_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GTK_BASE_SINK, GstGtkBaseSinkClass)) +#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_BASE_SINK)) +#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_BASE_SINK)) +#define GST_GTK_BASE_SINK_CAST(obj) ((GstGtkBaseSink*)(obj)) + +G_BEGIN_DECLS + +typedef struct _GstGtkBaseSink GstGtkBaseSink; +typedef struct _GstGtkBaseSinkClass GstGtkBaseSinkClass; + +GType gst_gtk_base_sink_get_type (void); + +/** + * GstGtkBaseSink: + * + * Opaque #GstGtkBaseSink object + */ +struct _GstGtkBaseSink +{ + /* */ + GstVideoSink parent; + + GstVideoInfo v_info; + + GtkGstBaseWidget *widget; + + /* properties */ + gboolean force_aspect_ratio; + GBinding *bind_aspect_ratio; + + gint par_n; + gint par_d; + GBinding *bind_pixel_aspect_ratio; + + gboolean ignore_alpha; + GBinding *bind_ignore_alpha; + + GtkWidget *window; + gulong widget_destroy_id; + gulong window_destroy_id; +}; + +/** + * GstGtkBaseSinkClass: + * + * The #GstGtkBaseSinkClass struct only contains private data + */ +struct _GstGtkBaseSinkClass +{ + GstVideoSinkClass object_class; + + /* metadata */ + const gchar *window_title; + + /* virtuals */ + GtkWidget* (*create_widget) (void); +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstGtkBaseSink, gst_object_unref) + +G_END_DECLS + +#endif /* __GST_GTK_BASE_SINK_H__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.c b/lib/gst/clapper/gtk4/gstgtkglsink.c new file mode 100644 index 00000000..e680c5a0 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkglsink.c @@ -0,0 +1,387 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-gtkglsink + * @title: gtkglsink + */ + +/** + * SECTION:element-gtk4glsink + * @title: gtk4glsink + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkconfig.h" +#include "gstgtkglsink.h" +#include "gtkgstglwidget.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink); +#define GST_CAT_DEFAULT gst_debug_gtk_gl_sink + +static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink); +static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink); +static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query); +static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, + GstQuery * query); +static GstCaps *gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, + GstCaps * filter); + +static void gst_gtk_gl_sink_finalize (GObject * object); + +static GstStaticPadTemplate gst_gtk_gl_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", " + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA"))); + +#define gst_gtk_gl_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink, + GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink, + GTKCONFIG_GLSINK, 0, GTKCONFIG_NAME " GL Video Sink")); + +static void +gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstGtkBaseSinkClass *gstgtkbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass; + + gobject_class->finalize = gst_gtk_gl_sink_finalize; + + gstbasesink_class->query = gst_gtk_gl_sink_query; + gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation; + gstbasesink_class->start = gst_gtk_gl_sink_start; + gstbasesink_class->stop = gst_gtk_gl_sink_stop; + gstbasesink_class->get_caps = gst_gtk_gl_sink_get_caps; + + gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new; + gstgtkbasesink_class->window_title = GTKCONFIG_NAME " GL Renderer"; + + gst_element_class_set_metadata (gstelement_class, + GTKCONFIG_NAME " GL Video Sink", + "Sink/Video", "A video sink that renders to a GtkWidget using OpenGL", + "Matthew Waters , " + "Rafał Dzięgiel "); + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_gtk_gl_sink_template); +} + +static void +gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink) +{ +} + +static gboolean +gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + gboolean res = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + { + if (gst_gl_handle_context_query ((GstElement *) gtk_sink, query, + gtk_sink->display, gtk_sink->context, gtk_sink->gtk_context)) + return TRUE; + break; + } + default: + res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); + break; + } + + return res; +} + +static void +_size_changed_cb (GtkWidget * widget, gint width, + gint height, GstGtkGLSink * gtk_sink) +{ + gboolean reconfigure; + + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + + /* Ignore size changes before widget is negotiated + * we are going to queue a resize after negotiation */ + if (!base_widget->negotiated) + return; + + GST_OBJECT_LOCK (gtk_sink); + reconfigure = + (width != gtk_sink->display_width || height != gtk_sink->display_height); + gtk_sink->display_width = width; + gtk_sink->display_height = height; + GST_OBJECT_UNLOCK (gtk_sink); + + if (reconfigure) { + GST_DEBUG_OBJECT (gtk_sink, "Sending reconfigure event on sinkpad"); + gst_pad_push_event (GST_BASE_SINK (gtk_sink)->sinkpad, + gst_event_new_reconfigure ()); + } +} + +static void +destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink) +{ + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (widget, gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->widget_destroy_sig_handler) { + g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler); + gtk_sink->widget_destroy_sig_handler = 0; + } +} + +static gboolean +gst_gtk_gl_sink_start (GstBaseSink * bsink) +{ + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GtkGstGLWidget *gst_widget; + + if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink)) + return FALSE; + + /* After this point, gtk_sink->widget will always be set */ + gst_widget = GTK_GST_GL_WIDGET (base_sink->widget); + + /* Track the allocation size */ + if (!gtk_sink->widget_resize_sig_handler) { + gtk_sink->widget_resize_sig_handler = + g_signal_connect (gst_widget, "resize", + G_CALLBACK (_size_changed_cb), gtk_sink); + } + + if (!gtk_sink->widget_destroy_sig_handler) { + gtk_sink->widget_destroy_sig_handler = + g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb), + gtk_sink); + } + + if (!gtk_gst_gl_widget_init_winsys (gst_widget)) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Failed to initialize OpenGL with GTK"), (NULL)); + return FALSE; + } + + if (!gtk_sink->display) + gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget); + if (!gtk_sink->context) + gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget); + if (!gtk_sink->gtk_context) + gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget); + + if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Failed to retrieve OpenGL context from GTK"), (NULL)); + return FALSE; + } + + gst_gl_element_propagate_display_context (GST_ELEMENT (bsink), + gtk_sink->display); + + return TRUE; +} + +static gboolean +gst_gtk_gl_sink_stop (GstBaseSink * bsink) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); + + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->display) { + gst_object_unref (gtk_sink->display); + gtk_sink->display = NULL; + } + + if (gtk_sink->context) { + gst_object_unref (gtk_sink->context); + gtk_sink->context = NULL; + } + + if (gtk_sink->gtk_context) { + gst_object_unref (gtk_sink->gtk_context); + gtk_sink->gtk_context = NULL; + } + + return GST_BASE_SINK_CLASS (parent_class)->stop (bsink); +} + +static gboolean +gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GstBufferPool *pool = NULL; + GstStructure *config; + GstCaps *caps; + GstVideoInfo info; + guint size; + gboolean need_pool; + GstStructure *allocation_meta = NULL; + gint display_width, display_height; + + if (!gtk_sink->display || !gtk_sink->context) + return FALSE; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (caps == NULL) + goto no_caps; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + /* the normal size of a frame */ + size = info.size; + + if (need_pool) { + GST_DEBUG_OBJECT (gtk_sink, "create new pool"); + pool = gst_gl_buffer_pool_new (gtk_sink->context); + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_GL_SYNC_META); + + if (!gst_buffer_pool_set_config (pool, config)) + goto config_failed; + } + + /* we need at least 2 buffer because we hold on to the last one */ + gst_query_add_allocation_pool (query, pool, size, 2, 0); + if (pool) + gst_object_unref (pool); + + GST_OBJECT_LOCK (gtk_sink); + display_width = gtk_sink->display_width; + display_height = gtk_sink->display_height; + GST_OBJECT_UNLOCK (gtk_sink); + + if (display_width != 0 && display_height != 0) { + GST_DEBUG_OBJECT (gtk_sink, "sending alloc query with size %dx%d", + display_width, display_height); + allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta", + "width", G_TYPE_UINT, display_width, + "height", G_TYPE_UINT, display_height, NULL); + } + + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta); + + if (allocation_meta) + gst_structure_free (allocation_meta); + + /* we also support various metadata */ + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0); + + if (gtk_sink->context->gl_vtable->FenceSync) + gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0); + + return TRUE; + + /* ERRORS */ +no_caps: + { + GST_DEBUG_OBJECT (bsink, "no caps specified"); + return FALSE; + } +invalid_caps: + { + GST_DEBUG_OBJECT (bsink, "invalid caps specified"); + return FALSE; + } +config_failed: + { + GST_DEBUG_OBJECT (bsink, "failed setting config"); + return FALSE; + } +} + +static GstCaps * +gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) +{ + GstCaps *tmp = NULL; + GstCaps *result = NULL; + + tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); + + if (filter) { + GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT, + filter); + + result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (tmp); + } else { + result = tmp; + } + + result = gst_gl_overlay_compositor_add_caps (result); + + GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result); + + return result; +} + +static void +gst_gtk_gl_sink_finalize (GObject * object) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object); + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object); + + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->widget_destroy_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_destroy_sig_handler); + gtk_sink->widget_destroy_sig_handler = 0; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.h b/lib/gst/clapper/gtk4/gstgtkglsink.h new file mode 100644 index 00000000..56595f23 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkglsink.h @@ -0,0 +1,65 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_GL_SINK_H__ +#define __GST_GTK_GL_SINK_H__ + +#include +#include +#include +#include +#include + +#include "gstgtkbasesink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_GTK_GL_SINK (gst_gtk_gl_sink_get_type ()) +G_DECLARE_FINAL_TYPE (GstGtkGLSink, gst_gtk_gl_sink, GST, GTK_GL_SINK, + GstGtkBaseSink); + +/** + * GstGtkGLSink: + * + * Opaque #GstGtkGLSink object + */ +struct _GstGtkGLSink +{ + /* */ + GstGtkBaseSink parent; + + GstGLDisplay *display; + GstGLContext *context; + GstGLContext *gtk_context; + + GstGLUpload *upload; + GstBuffer *uploaded_buffer; + + /* read/write with object lock */ + gint display_width; + gint display_height; + + gulong widget_resize_sig_handler; + gulong widget_destroy_sig_handler; +}; + +G_END_DECLS + +#endif /* __GST_GTK_GL_SINK_H__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkutils.c b/lib/gst/clapper/gtk4/gstgtkutils.c new file mode 100644 index 00000000..c730f018 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkutils.c @@ -0,0 +1,71 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstgtkutils.h" + +struct invoke_context +{ + GThreadFunc func; + gpointer data; + GMutex lock; + GCond cond; + gboolean fired; + + gpointer res; +}; + +static gboolean +gst_gtk_invoke_func (struct invoke_context *info) +{ + g_mutex_lock (&info->lock); + info->res = info->func (info->data); + info->fired = TRUE; + g_cond_signal (&info->cond); + g_mutex_unlock (&info->lock); + + return G_SOURCE_REMOVE; +} + +gpointer +gst_gtk_invoke_on_main (GThreadFunc func, gpointer data) +{ + GMainContext *main_context = g_main_context_default (); + struct invoke_context info; + + g_mutex_init (&info.lock); + g_cond_init (&info.cond); + info.fired = FALSE; + info.func = func; + info.data = data; + + g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func, + &info); + + g_mutex_lock (&info.lock); + while (!info.fired) + g_cond_wait (&info.cond, &info.lock); + g_mutex_unlock (&info.lock); + + g_mutex_clear (&info.lock); + g_cond_clear (&info.cond); + + return info.res; +} diff --git a/lib/gst/clapper/gtk4/gstgtkutils.h b/lib/gst/clapper/gtk4/gstgtkutils.h new file mode 100644 index 00000000..7584ae2c --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkutils.h @@ -0,0 +1,29 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_UTILS_H__ +#define __GST_GTK_UTILS_H__ + +#include + +gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data); + +#endif /* __GST_GTK_UTILS_H__ */ diff --git a/lib/gst/clapper/gtk4/gtkconfig.h b/lib/gst/clapper/gtk4/gtkconfig.h new file mode 100644 index 00000000..ecbf9558 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkconfig.h @@ -0,0 +1,31 @@ +/* + * GStreamer + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#if defined(BUILD_FOR_GTK4) +#define GTKCONFIG_PLUGIN gtk4 +#define GTKCONFIG_NAME "GTK4" +#define GTKCONFIG_SINK "gtk4sink" +#define GTKCONFIG_GLSINK "gtk4glsink" +#else +#define GTKCONFIG_PLUGIN gtk +#define GTKCONFIG_NAME "GTK" +#define GTKCONFIG_SINK "gtksink" +#define GTKCONFIG_GLSINK "gtkglsink" +#endif diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.c b/lib/gst/clapper/gtk4/gtkgstbasewidget.c new file mode 100644 index 00000000..374eb7f9 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.c @@ -0,0 +1,605 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkgstbasewidget.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget); +#define GST_CAT_DEFAULT gst_debug_gtk_base_widget + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 +#define DEFAULT_IGNORE_ALPHA TRUE + +enum +{ + PROP_0, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_IGNORE_ALPHA, +}; + +static void +gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min, + gint * natural) +{ + GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget; + gint video_width = gst_widget->display_width; + + if (!gst_widget->negotiated) + video_width = 10; + + if (min) + *min = 1; + if (natural) + *natural = video_width; +} + +static void +gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min, + gint * natural) +{ + GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget; + gint video_height = gst_widget->display_height; + + if (!gst_widget->negotiated) + video_height = 10; + + if (min) + *min = 1; + if (natural) + *natural = video_height; +} + +#if defined(BUILD_FOR_GTK4) +static void +gtk_gst_base_widget_measure (GtkWidget * widget, GtkOrientation orientation, + gint for_size, gint * min, gint * natural, + gint * minimum_baseline, gint * natural_baseline) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_gst_base_widget_get_preferred_width (widget, min, natural); + else + gtk_gst_base_widget_get_preferred_height (widget, min, natural); + + *minimum_baseline = -1; + *natural_baseline = -1; +} +#endif + +static void +gtk_gst_base_widget_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + gtk_widget->force_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_PIXEL_ASPECT_RATIO: + gtk_widget->par_n = gst_value_get_fraction_numerator (value); + gtk_widget->par_d = gst_value_get_fraction_denominator (value); + break; + case PROP_IGNORE_ALPHA: + gtk_widget->ignore_alpha = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_gst_base_widget_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, gtk_widget->force_aspect_ratio); + break; + case PROP_PIXEL_ASPECT_RATIO: + gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d); + break; + case PROP_IGNORE_ALPHA: + g_value_set_boolean (value, gtk_widget->ignore_alpha); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info) +{ + gboolean ok; + gint width, height; + gint par_n, par_d; + gint display_par_n, display_par_d; + + width = GST_VIDEO_INFO_WIDTH (info); + height = GST_VIDEO_INFO_HEIGHT (info); + + par_n = GST_VIDEO_INFO_PAR_N (info); + par_d = GST_VIDEO_INFO_PAR_D (info); + + if (!par_n) + par_n = 1; + + /* get display's PAR */ + if (widget->par_n != 0 && widget->par_d != 0) { + display_par_n = widget->par_n; + display_par_d = widget->par_d; + } else { + display_par_n = 1; + display_par_d = 1; + } + + + ok = gst_video_calculate_display_ratio (&widget->display_ratio_num, + &widget->display_ratio_den, width, height, par_n, par_d, display_par_n, + display_par_d); + + if (ok) { + GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n, + display_par_d); + return TRUE; + } + + return FALSE; +} + +static void +_apply_par (GtkGstBaseWidget * widget) +{ + guint display_ratio_num, display_ratio_den; + gint width, height; + + width = GST_VIDEO_INFO_WIDTH (&widget->v_info); + height = GST_VIDEO_INFO_HEIGHT (&widget->v_info); + + display_ratio_num = widget->display_ratio_num; + display_ratio_den = widget->display_ratio_den; + + if (height % display_ratio_den == 0) { + GST_DEBUG ("keeping video height"); + widget->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->display_height = height; + } else if (width % display_ratio_num == 0) { + GST_DEBUG ("keeping video width"); + widget->display_width = width; + widget->display_height = (guint) + gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num); + } else { + GST_DEBUG ("approximating while keeping video height"); + widget->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->display_height = height; + } + + GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height); +} + +static gboolean +_queue_draw (GtkGstBaseWidget * widget) +{ + GTK_GST_BASE_WIDGET_LOCK (widget); + widget->draw_id = 0; + + if (widget->pending_resize) { + widget->pending_resize = FALSE; + + widget->v_info = widget->pending_v_info; + widget->negotiated = TRUE; + + _apply_par (widget); + + gtk_widget_queue_resize (GTK_WIDGET (widget)); + } else { + gtk_widget_queue_draw (GTK_WIDGET (widget)); + } + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + + return G_SOURCE_REMOVE; +} + +static const gchar * +_gdk_key_to_navigation_string (guint keyval) +{ + /* TODO: expand */ + switch (keyval) { +#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key) + KEY (Up); + KEY (Down); + KEY (Left); + KEY (Right); + KEY (Home); + KEY (End); +#undef KEY + default: + return NULL; + } +} + +static GdkEvent * +_get_current_event (GtkEventController * controller) +{ +#if defined(BUILD_FOR_GTK4) + return gtk_event_controller_get_current_event (controller); +#else + return gtk_get_current_event (); +#endif +} + +static void +_gdk_event_free (GdkEvent * event) +{ +#if !defined(BUILD_FOR_GTK4) + if (event) + gdk_event_free (event); +#endif +} + +static gboolean +gtk_gst_base_widget_key_event (GtkEventControllerKey * key_controller, + guint keyval, guint keycode, GdkModifierType state) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (key_controller); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + GdkEvent *event = _get_current_event (controller); + const gchar *str = _gdk_key_to_navigation_string (keyval); + + if (str) { + const gchar *key_type = + gdk_event_get_event_type (event) == + GDK_KEY_PRESS ? "key-press" : "key-release"; + gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str); + } + _gdk_event_free (event); + } + g_object_unref (element); + } + + return FALSE; +} + +static void +_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget, + GtkAllocation * allocation, GstVideoRectangle * result) +{ + if (base_widget->force_aspect_ratio) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = base_widget->display_width; + src.h = base_widget->display_height; + + dst.x = 0; + dst.y = 0; + dst.w = allocation->width; + dst.h = allocation->height; + + gst_video_sink_center_rect (src, dst, result, TRUE); + } else { + result->x = 0; + result->y = 0; + result->w = allocation->width; + result->h = allocation->height; + } +} + +static void +_display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x, + gdouble y, gdouble * stream_x, gdouble * stream_y) +{ + gdouble stream_width, stream_height; + GtkAllocation allocation; + GstVideoRectangle result; + + gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation); + _fit_stream_to_allocated_size (base_widget, &allocation, &result); + + stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info); + stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info); + + /* from display coordinates to stream coordinates */ + if (result.w > 0) + *stream_x = (x - result.x) / result.w * stream_width; + else + *stream_x = 0.; + + /* clip to stream size */ + if (*stream_x < 0.) + *stream_x = 0.; + if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info)) + *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info); + + /* same for y-axis */ + if (result.h > 0) + *stream_y = (y - result.y) / result.h * stream_height; + else + *stream_y = 0.; + + if (*stream_y < 0.) + *stream_y = 0.; + if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info)) + *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info); + + GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y); +} + +static gboolean +gtk_gst_base_widget_button_event ( +#if defined(BUILD_FOR_GTK4) + GtkGestureClick * gesture, +#else + GtkGestureMultiPress * gesture, +#endif + gint n_press, gdouble x, gdouble y) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + GdkEvent *event = _get_current_event (controller); + const gchar *key_type = + gdk_event_get_event_type (event) == GDK_BUTTON_PRESS + ? "mouse-button-press" : "mouse-button-release"; + gdouble stream_x, stream_y; +#if !defined(BUILD_FOR_GTK4) + guint button; + gdk_event_get_button (event, &button); +#endif + + _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y); + + gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type, +#if defined(BUILD_FOR_GTK4) + /* Gesture is set to ignore other buttons so we do not have to check */ + GDK_BUTTON_PRIMARY, +#else + button, +#endif + stream_x, stream_y); + + _gdk_event_free (event); + } + g_object_unref (element); + } + + return FALSE; +} + +static gboolean +gtk_gst_base_widget_motion_event (GtkEventControllerMotion * motion_controller, + gdouble x, gdouble y) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (motion_controller); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + gdouble stream_x, stream_y; + + _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y); + + gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move", + 0, stream_x, stream_y); + } + g_object_unref (element); + } + + return FALSE; +} + +void +gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass) +{ + GObjectClass *gobject_klass = (GObjectClass *) klass; + GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass; + + gobject_klass->set_property = gtk_gst_base_widget_set_property; + gobject_klass->get_property = gtk_gst_base_widget_get_property; + + g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + DEFAULT_FORCE_ASPECT_RATIO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO, + gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, + G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA, + g_param_spec_boolean ("ignore-alpha", "Ignore Alpha", + "When enabled, alpha will be ignored and converted to black", + DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + +#if defined(BUILD_FOR_GTK4) + widget_klass->measure = gtk_gst_base_widget_measure; +#else + widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width; + widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height; +#endif + + GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0, + "GTK Video Base Widget"); +} + +void +gtk_gst_base_widget_init (GtkGstBaseWidget * widget) +{ + widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + widget->par_n = DEFAULT_PAR_N; + widget->par_d = DEFAULT_PAR_D; + widget->ignore_alpha = DEFAULT_IGNORE_ALPHA; + + gst_video_info_init (&widget->v_info); + gst_video_info_init (&widget->pending_v_info); + + g_weak_ref_init (&widget->element, NULL); + g_mutex_init (&widget->lock); + + widget->key_controller = gtk_event_controller_key_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WIDGET (widget) +#endif + ); + g_signal_connect (widget->key_controller, "key-pressed", + G_CALLBACK (gtk_gst_base_widget_key_event), NULL); + g_signal_connect (widget->key_controller, "key-released", + G_CALLBACK (gtk_gst_base_widget_key_event), NULL); + + widget->motion_controller = gtk_event_controller_motion_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WIDGET (widget) +#endif + ); + g_signal_connect (widget->motion_controller, "motion", + G_CALLBACK (gtk_gst_base_widget_motion_event), NULL); + + widget->click_gesture = +#if defined(BUILD_FOR_GTK4) + gtk_gesture_click_new (); +#else + gtk_gesture_multi_press_new (GTK_WIDGET (widget)); +#endif + g_signal_connect (widget->click_gesture, "pressed", + G_CALLBACK (gtk_gst_base_widget_button_event), NULL); + g_signal_connect (widget->click_gesture, "released", + G_CALLBACK (gtk_gst_base_widget_button_event), NULL); + +#if defined(BUILD_FOR_GTK4) + /* Otherwise widget in grid will appear as a 1x1px + * video which might be misleading for users */ + gtk_widget_set_hexpand (GTK_WIDGET (widget), TRUE); + gtk_widget_set_vexpand (GTK_WIDGET (widget), TRUE); + + gtk_widget_set_focusable (GTK_WIDGET (widget), TRUE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (widget->click_gesture), + GDK_BUTTON_PRIMARY); + + gtk_widget_add_controller (GTK_WIDGET (widget), widget->key_controller); + gtk_widget_add_controller (GTK_WIDGET (widget), widget->motion_controller); + gtk_widget_add_controller (GTK_WIDGET (widget), + GTK_EVENT_CONTROLLER (widget->click_gesture)); +#endif + + gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE); +} + +void +gtk_gst_base_widget_finalize (GObject * object) +{ + GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object); + + /* GTK4 takes ownership of EventControllers + * while GTK3 still needs manual unref */ +#if !defined(BUILD_FOR_GTK4) + g_object_unref (widget->key_controller); + g_object_unref (widget->motion_controller); + g_object_unref (widget->click_gesture); +#endif + + gst_buffer_replace (&widget->pending_buffer, NULL); + gst_buffer_replace (&widget->buffer, NULL); + g_mutex_clear (&widget->lock); + g_weak_ref_clear (&widget->element); + + if (widget->draw_id) + g_source_remove (widget->draw_id); +} + +void +gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, + GstElement * element) +{ + g_weak_ref_set (&widget->element, element); +} + +gboolean +gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, + GstVideoInfo * v_info) +{ + GTK_GST_BASE_WIDGET_LOCK (widget); + + if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) { + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return TRUE; + } + + if (!_calculate_par (widget, v_info)) { + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return FALSE; + } + + widget->pending_resize = TRUE; + widget->pending_v_info = *v_info; + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + + return TRUE; +} + +void +gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer) +{ + /* As we have no type, this is better then no check */ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + GTK_GST_BASE_WIDGET_LOCK (widget); + + gst_buffer_replace (&widget->pending_buffer, buffer); + + if (!widget->draw_id) { + widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) _queue_draw, widget, NULL); + } + + GTK_GST_BASE_WIDGET_UNLOCK (widget); +} diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.h b/lib/gst/clapper/gtk4/gtkgstbasewidget.h new file mode 100644 index 00000000..e96d2480 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.h @@ -0,0 +1,101 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GTK_GST_BASE_WIDGET_H__ +#define __GTK_GST_BASE_WIDGET_H__ + +#include +#include +#include + +#if !defined(BUILD_FOR_GTK4) +#include +#endif + +#define GTK_GST_BASE_WIDGET(w) ((GtkGstBaseWidget *)(w)) +#define GTK_GST_BASE_WIDGET_CLASS(k) ((GtkGstBaseWidgetClass *)(k)) +#define GTK_GST_BASE_WIDGET_LOCK(w) g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock) +#define GTK_GST_BASE_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock) + +G_BEGIN_DECLS + +typedef struct _GtkGstBaseWidget GtkGstBaseWidget; +typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass; + +struct _GtkGstBaseWidget +{ + union { + GtkGLArea gl_area; + } parent; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + gboolean ignore_alpha; + + gint display_width; + gint display_height; + + gboolean negotiated; + GstBuffer *pending_buffer; + GstBuffer *buffer; + GstVideoInfo v_info; + + /* resize */ + gboolean pending_resize; + GstVideoInfo pending_v_info; + guint display_ratio_num; + guint display_ratio_den; + + /*< private >*/ + GMutex lock; + GWeakRef element; + + /* event controllers */ + GtkEventController *key_controller; + GtkEventController *motion_controller; + GtkGesture *click_gesture; + + /* Pending draw idles callback */ + guint draw_id; +}; + +struct _GtkGstBaseWidgetClass +{ + union { + GtkGLAreaClass gl_area_class; + } parent_class; +}; + +/* For implementer */ +void gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass); +void gtk_gst_base_widget_init (GtkGstBaseWidget * widget); + +void gtk_gst_base_widget_finalize (GObject * object); + +/* API */ +gboolean gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, GstVideoInfo * v_info); +void gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer); +void gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, GstElement * element); + +G_END_DECLS + +#endif /* __GTK_GST_BASE_WIDGET_H__ */ diff --git a/lib/gst/clapper/gtk4/gtkgstglwidget.c b/lib/gst/clapper/gtk4/gtkgstglwidget.c new file mode 100644 index 00000000..a9250676 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstglwidget.c @@ -0,0 +1,592 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkgstglwidget.h" +#include "gstgtkutils.h" +#include +#include + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) +#if defined(BUILD_FOR_GTK4) +#include +#else +#include +#endif +#include +#endif + +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) +#if defined(BUILD_FOR_GTK4) +#include +#else +#include +#endif +#include +#endif + +/** + * SECTION:gtkgstglwidget + * @title: GtkGstGlWidget + * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers + * @see_also: #GtkGLArea, #GstBuffer + * + * #GtkGstGLWidget is an #GtkWidget that renders GStreamer video buffers. + */ + +#define GST_CAT_DEFAULT gtk_gst_gl_widget_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _GtkGstGLWidgetPrivate +{ + gboolean initted; + GstGLDisplay *display; + GdkGLContext *gdk_context; + GstGLContext *other_context; + GstGLContext *context; + GstGLUpload *upload; + GstGLShader *shader; + GLuint vao; + GLuint vertex_buffer; + GLint attr_position; + GLint attr_texture; + GLuint current_tex; + GstGLOverlayCompositor *overlay_compositor; +}; + +static const GLfloat vertices[] = { + 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 1.0f +}; + +G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA, + G_ADD_PRIVATE (GtkGstGLWidget) + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0, + "GTK Gst GL Widget")); + +static void +gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + + gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer); + + /* Load the vertex position */ + gl->VertexAttribPointer (priv->attr_position, 3, GL_FLOAT, GL_FALSE, + 5 * sizeof (GLfloat), (void *) 0); + + /* Load the texture coordinate */ + gl->VertexAttribPointer (priv->attr_texture, 2, GL_FLOAT, GL_FALSE, + 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat))); + + gl->EnableVertexAttribArray (priv->attr_position); + gl->EnableVertexAttribArray (priv->attr_texture); +} + +static void +gtk_gst_gl_widget_unbind_buffer (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + + gl->BindBuffer (GL_ARRAY_BUFFER, 0); + + gl->DisableVertexAttribArray (priv->attr_position); + gl->DisableVertexAttribArray (priv->attr_texture); +} + +static void +gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + GError *error = NULL; + + gst_gl_insert_debug_marker (priv->other_context, "initializing redisplay"); + if (!(priv->shader = gst_gl_shader_new_default (priv->context, &error))) { + GST_ERROR ("Failed to initialize shader: %s", error->message); + return; + } + + priv->attr_position = + gst_gl_shader_get_attribute_location (priv->shader, "a_position"); + priv->attr_texture = + gst_gl_shader_get_attribute_location (priv->shader, "a_texcoord"); + + if (gl->GenVertexArrays) { + gl->GenVertexArrays (1, &priv->vao); + gl->BindVertexArray (priv->vao); + } + + gl->GenBuffers (1, &priv->vertex_buffer); + gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer); + gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices, + GL_STATIC_DRAW); + + if (gl->GenVertexArrays) { + gtk_gst_gl_widget_bind_buffer (gst_widget); + gl->BindVertexArray (0); + } + + gl->BindBuffer (GL_ARRAY_BUFFER, 0); + + priv->overlay_compositor = + gst_gl_overlay_compositor_new (priv->other_context); + + priv->initted = TRUE; +} + +static void +_redraw_texture (GtkGstGLWidget * gst_widget, guint tex) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + const GLushort indices[] = { 0, 1, 2, 0, 2, 3 }; + + if (gst_widget->base.force_aspect_ratio) { + GstVideoRectangle src, dst, result; + gint widget_width, widget_height, widget_scale; + + gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gl->Clear (GL_COLOR_BUFFER_BIT); + + widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget); + widget_width = gtk_widget_get_allocated_width ((GtkWidget *) gst_widget); + widget_height = gtk_widget_get_allocated_height ((GtkWidget *) gst_widget); + + src.x = 0; + src.y = 0; + src.w = gst_widget->base.display_width; + src.h = gst_widget->base.display_height; + + dst.x = 0; + dst.y = 0; + dst.w = widget_width * widget_scale; + dst.h = widget_height * widget_scale; + + gst_video_sink_center_rect (src, dst, &result, TRUE); + + gl->Viewport (result.x, result.y, result.w, result.h); + } + + gst_gl_shader_use (priv->shader); + + if (gl->BindVertexArray) + gl->BindVertexArray (priv->vao); + gtk_gst_gl_widget_bind_buffer (gst_widget); + + gl->ActiveTexture (GL_TEXTURE0); + gl->BindTexture (GL_TEXTURE_2D, tex); + gst_gl_shader_set_uniform_1i (priv->shader, "tex", 0); + + gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); + + if (gl->BindVertexArray) + gl->BindVertexArray (0); + else + gtk_gst_gl_widget_unbind_buffer (gst_widget); + + gl->BindTexture (GL_TEXTURE_2D, 0); +} + +static inline void +_draw_black (GstGLContext * context) +{ + const GstGLFuncs *gl = context->gl_vtable; + + gst_gl_insert_debug_marker (context, "no buffer. rendering black"); + gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gl->Clear (GL_COLOR_BUFFER_BIT); +} + +static gboolean +gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context) +{ + GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (widget)->priv; + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + + GTK_GST_BASE_WIDGET_LOCK (widget); + + if (!priv->context || !priv->other_context) + goto done; + + gst_gl_context_activate (priv->other_context, TRUE); + + if (!priv->initted) + gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget)); + + if (!priv->initted || !base_widget->negotiated) { + _draw_black (priv->other_context); + goto done; + } + + /* Upload latest buffer */ + if (base_widget->pending_buffer) { + GstBuffer *buffer = base_widget->pending_buffer; + GstVideoFrame gl_frame; + GstGLSyncMeta *sync_meta; + + if (!gst_video_frame_map (&gl_frame, &base_widget->v_info, buffer, + GST_MAP_READ | GST_MAP_GL)) { + _draw_black (priv->other_context); + goto done; + } + + priv->current_tex = *(guint *) gl_frame.data[0]; + gst_gl_insert_debug_marker (priv->other_context, "redrawing texture %u", + priv->current_tex); + + gst_gl_overlay_compositor_upload_overlays (priv->overlay_compositor, + buffer); + + sync_meta = gst_buffer_get_gl_sync_meta (buffer); + if (sync_meta) { + /* XXX: the set_sync() seems to be needed for resizing */ + gst_gl_sync_meta_set_sync_point (sync_meta, priv->context); + gst_gl_sync_meta_wait (sync_meta, priv->other_context); + } + + gst_video_frame_unmap (&gl_frame); + + if (base_widget->buffer) + gst_buffer_unref (base_widget->buffer); + + /* Keep the buffer to ensure current_tex stay valid */ + base_widget->buffer = buffer; + base_widget->pending_buffer = NULL; + } + + GST_DEBUG ("rendering buffer %p with gdk context %p", + base_widget->buffer, context); + + _redraw_texture (GTK_GST_GL_WIDGET (widget), priv->current_tex); + gst_gl_overlay_compositor_draw_overlays (priv->overlay_compositor); + + gst_gl_insert_debug_marker (priv->other_context, "texture %u redrawn", + priv->current_tex); + +done: + if (priv->other_context) + gst_gl_context_activate (priv->other_context, FALSE); + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return FALSE; +} + +static void +_reset_gl (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->other_context->gl_vtable; + + if (!priv->gdk_context) + priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget)); + + if (priv->gdk_context == NULL) + return; + + gdk_gl_context_make_current (priv->gdk_context); + gst_gl_context_activate (priv->other_context, TRUE); + + if (priv->vao) { + gl->DeleteVertexArrays (1, &priv->vao); + priv->vao = 0; + } + + if (priv->vertex_buffer) { + gl->DeleteBuffers (1, &priv->vertex_buffer); + priv->vertex_buffer = 0; + } + + if (priv->upload) { + gst_object_unref (priv->upload); + priv->upload = NULL; + } + + if (priv->shader) { + gst_object_unref (priv->shader); + priv->shader = NULL; + } + + if (priv->overlay_compositor) + gst_object_unref (priv->overlay_compositor); + + gst_gl_context_activate (priv->other_context, FALSE); + + gst_object_unref (priv->other_context); + priv->other_context = NULL; + + gdk_gl_context_clear_current (); + + g_object_unref (priv->gdk_context); + priv->gdk_context = NULL; +} + +static void +gtk_gst_gl_widget_finalize (GObject * object) +{ + GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (object)->priv; + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (object); + + if (priv->other_context) + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _reset_gl, base_widget); + + if (priv->context) + gst_object_unref (priv->context); + + if (priv->display) + gst_object_unref (priv->display); + + gtk_gst_base_widget_finalize (object); + G_OBJECT_CLASS (gtk_gst_gl_widget_parent_class)->finalize (object); +} + +static void +gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass) +{ + GObjectClass *gobject_klass = (GObjectClass *) klass; + GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass; + + gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass)); + + gobject_klass->finalize = gtk_gst_gl_widget_finalize; + gl_widget_klass->render = gtk_gst_gl_widget_render; +} + +static void +gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget) +{ + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (gst_widget); + GdkDisplay *display; + GtkGstGLWidgetPrivate *priv; + + gtk_gst_base_widget_init (base_widget); + + gst_widget->priv = priv = gtk_gst_gl_widget_get_instance_private (gst_widget); + + display = gdk_display_get_default (); + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (display)) { + priv->display = (GstGLDisplay *) + gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay + (display)); + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (display)) { + struct wl_display *wayland_display = + gdk_wayland_display_get_wl_display (display); + priv->display = (GstGLDisplay *) + gst_gl_display_wayland_new_with_display (wayland_display); + } +#endif + + (void) display; + + if (!priv->display) + priv->display = gst_gl_display_new (); + + GST_INFO ("Created %" GST_PTR_FORMAT, priv->display); + + /* GTK4 always has alpha */ +#if !defined(BUILD_FOR_GTK4) + gtk_gl_area_set_has_alpha (GTK_GL_AREA (gst_widget), + !base_widget->ignore_alpha); +#endif +} + +static void +_get_gl_context (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + GstGLPlatform platform = GST_GL_PLATFORM_NONE; + GstGLAPI gl_api = GST_GL_API_NONE; + guintptr gl_handle = 0; + + gtk_widget_realize (GTK_WIDGET (gst_widget)); + + if (priv->other_context) + gst_object_unref (priv->other_context); + priv->other_context = NULL; + + if (priv->gdk_context) + g_object_unref (priv->gdk_context); + + priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget)); + if (priv->gdk_context == NULL) { + GError *error = gtk_gl_area_get_error (GTK_GL_AREA (gst_widget)); + + GST_ERROR_OBJECT (gst_widget, "Error creating GdkGLContext : %s", + error ? error->message : "No error set by Gdk"); + g_clear_error (&error); + return; + } + + g_object_ref (priv->gdk_context); + + gdk_gl_context_make_current (priv->gdk_context); + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GST_IS_GL_DISPLAY_X11 (priv->display)) { +#if GST_GL_HAVE_PLATFORM_GLX + if (!gl_handle) { + platform = GST_GL_PLATFORM_GLX; + gl_handle = gst_gl_context_get_current_gl_context (platform); + } +#endif + +#if GST_GL_HAVE_PLATFORM_EGL + if (!gl_handle) { + platform = GST_GL_PLATFORM_EGL; + gl_handle = gst_gl_context_get_current_gl_context (platform); + } +#endif + + if (gl_handle) { + gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); + priv->other_context = + gst_gl_context_new_wrapped (priv->display, gl_handle, + platform, gl_api); + } + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND) + if (GST_IS_GL_DISPLAY_WAYLAND (priv->display)) { + platform = GST_GL_PLATFORM_EGL; + gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); + gl_handle = gst_gl_context_get_current_gl_context (platform); + if (gl_handle) + priv->other_context = + gst_gl_context_new_wrapped (priv->display, gl_handle, + platform, gl_api); + } +#endif + + (void) platform; + (void) gl_api; + (void) gl_handle; + + if (priv->other_context) { + GError *error = NULL; + + GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, + priv->other_context); + gst_gl_context_activate (priv->other_context, TRUE); + if (!gst_gl_context_fill_info (priv->other_context, &error)) { + GST_ERROR ("failed to retrieve gdk context info: %s", error->message); + g_clear_error (&error); + g_object_unref (priv->other_context); + priv->other_context = NULL; + } else { + gst_gl_context_activate (priv->other_context, FALSE); + } + } else { + GST_WARNING ("Could not retrieve Gdk OpenGL context"); + } +} + +GtkWidget * +gtk_gst_gl_widget_new (void) +{ + return (GtkWidget *) g_object_new (GTK_TYPE_GST_GL_WIDGET, NULL); +} + +gboolean +gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + GError *error = NULL; + + g_return_val_if_fail (GTK_IS_GST_GL_WIDGET (gst_widget), FALSE); + g_return_val_if_fail (priv->display != NULL, FALSE); + + GTK_GST_BASE_WIDGET_LOCK (gst_widget); + + if (priv->display && priv->gdk_context && priv->other_context) { + GST_TRACE ("have already initialized contexts"); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return TRUE; + } + + if (!priv->other_context) { + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _get_gl_context, gst_widget); + GTK_GST_BASE_WIDGET_LOCK (gst_widget); + } + + if (!GST_IS_GL_CONTEXT (priv->other_context)) { + GST_FIXME ("Could not retrieve Gdk OpenGL context"); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return FALSE; + } + + GST_OBJECT_LOCK (priv->display); + if (!gst_gl_display_create_context (priv->display, priv->other_context, + &priv->context, &error)) { + GST_WARNING ("Could not create OpenGL context: %s", + error ? error->message : "Unknown"); + g_clear_error (&error); + GST_OBJECT_UNLOCK (priv->display); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return FALSE; + } + gst_gl_display_add_context (priv->display, priv->context); + GST_OBJECT_UNLOCK (priv->display); + + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return TRUE; +} + +GstGLContext * +gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->other_context) + return NULL; + + return gst_object_ref (gst_widget->priv->other_context); +} + +GstGLContext * +gtk_gst_gl_widget_get_context (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->context) + return NULL; + + return gst_object_ref (gst_widget->priv->context); +} + +GstGLDisplay * +gtk_gst_gl_widget_get_display (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->display) + return NULL; + + return gst_object_ref (gst_widget->priv->display); +} diff --git a/lib/gst/clapper/gtk4/gtkgstglwidget.h b/lib/gst/clapper/gtk4/gtkgstglwidget.h new file mode 100644 index 00000000..7f055c48 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstglwidget.h @@ -0,0 +1,77 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GTK_GST_GL_WIDGET_H__ +#define __GTK_GST_GL_WIDGET_H__ + +#include +#include +#include + +#include "gtkgstbasewidget.h" + +G_BEGIN_DECLS + +GType gtk_gst_gl_widget_get_type (void); +#define GTK_TYPE_GST_GL_WIDGET (gtk_gst_gl_widget_get_type()) +#define GTK_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidget)) +#define GTK_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidgetClass)) +#define GTK_IS_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_GL_WIDGET)) +#define GTK_IS_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_GL_WIDGET)) +#define GTK_GST_GL_WIDGET_CAST(obj) ((GtkGstGLWidget*)(obj)) + +typedef struct _GtkGstGLWidget GtkGstGLWidget; +typedef struct _GtkGstGLWidgetClass GtkGstGLWidgetClass; +typedef struct _GtkGstGLWidgetPrivate GtkGstGLWidgetPrivate; + +/** + * GtkGstGLWidget: + * + * Opaque #GtkGstGLWidget object + */ +struct _GtkGstGLWidget +{ + /* */ + GtkGstBaseWidget base; + + GtkGstGLWidgetPrivate *priv; +}; + +/** + * GtkGstGLWidgetClass: + * + * The #GtkGstGLWidgetClass struct only contains private data + */ +struct _GtkGstGLWidgetClass +{ + /* */ + GtkGstBaseWidgetClass base_class; +}; + +GtkWidget * gtk_gst_gl_widget_new (void); + +gboolean gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * widget); +GstGLDisplay * gtk_gst_gl_widget_get_display (GtkGstGLWidget * widget); +GstGLContext * gtk_gst_gl_widget_get_context (GtkGstGLWidget * widget); +GstGLContext * gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * widget); + +G_END_DECLS + +#endif /* __GTK_GST_GL_WIDGET_H__ */ diff --git a/lib/gst/clapper/meson.build b/lib/gst/clapper/meson.build index fc93b596..1c6d9384 100644 --- a/lib/gst/clapper/meson.build +++ b/lib/gst/clapper/meson.build @@ -6,8 +6,14 @@ gstclapper_sources = [ 'gstclapper-g-main-context-signal-dispatcher.c', 'gstclapper-video-overlay-video-renderer.c', 'gstclapper-visualization.c', -] + 'gstclapper-gtk4-plugin.c', + 'gtk4/gstgtkbasesink.c', + 'gtk4/gstgtkutils.c', + 'gtk4/gtkgstbasewidget.c', + 'gtk4/gstgtkglsink.c', + 'gtk4/gtkgstglwidget.c', +] gstclapper_headers = [ 'clapper.h', 'clapper-prelude.h', @@ -19,20 +25,57 @@ gstclapper_headers = [ 'gstclapper-g-main-context-signal-dispatcher.h', 'gstclapper-video-overlay-video-renderer.h', 'gstclapper-visualization.h', + 'gstclapper-gtk4-plugin.h', ] +gstclapper_defines = [ + '-DHAVE_CONFIG_H', + '-DBUILDING_GST_CLAPPER', + '-DGST_USE_UNSTABLE_API', + '-DHAVE_GTK_GL', + '-DBUILD_FOR_GTK4', +] +gtk_deps = [gstgl_dep, gstglproto_dep] +have_gtk_gl_windowing = false -if not build_gir - error('Clapper requires GI bindings to be compiled') +gtk4_dep = dependency('gtk4', required : true) + +if not gtk4_dep.found() or not gtk4_dep.version().version_compare('>=4.0.0') + error('GTK4 is missing or is too old') +endif + +if not have_gstgl + error('GstGL is missing') +endif + +if gst_gl_have_window_x11 and gst_gl_have_platform_glx + gtk_x11_dep = dependency('gtk4-x11', required : false) + if gtk_x11_dep.found() + gtk_deps += [gtk_x11_dep, gstglx11_dep] + have_gtk_gl_windowing = true + endif +endif + +if gst_gl_have_window_wayland and gst_gl_have_platform_egl + gtk_wayland_dep = dependency('gtk4-wayland', required : false) + if gtk_wayland_dep.found() + gtk_deps += [gtk_wayland_dep, gstglegl_dep, gstglwayland_dep] + have_gtk_gl_windowing = true + endif +endif + +if not have_gtk_gl_windowing + error('GTK4 widget requires GL windowing') endif gstclapper = library('gstclapper-' + api_version, gstclapper_sources, - c_args : gst_clapper_args + ['-DBUILDING_GST_CLAPPER'], + c_args : gstclapper_defines, + link_args : noseh_link_args, include_directories : [configinc, libsinc], version : libversion, install : true, - dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep, - gsttag_dep, gstpbutils_dep], + dependencies : [gtk4_dep, gstbase_dep, gstvideo_dep, gstaudio_dep, + gsttag_dep, gstpbutils_dep, libm] + gtk_deps, ) clapper_gir = gnome.generate_gir(gstclapper, diff --git a/lib/meson.build b/lib/meson.build index a14f258d..bcbc3fbe 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -28,10 +28,12 @@ if cc.get_id() == 'msvc' '/w14189', # 'identifier' : local variable is initialized but not referenced ] add_project_arguments(msvc_args, language: ['c', 'cpp']) + noseh_link_args = ['/SAFESEH:NO'] else if cxx.has_argument('-Wno-non-virtual-dtor') add_project_arguments('-Wno-non-virtual-dtor', language: 'cpp') endif + noseh_link_args = [] endif if cc.has_link_argument('-Wl,-Bsymbolic-functions') @@ -153,13 +155,11 @@ warning_flags = [ '-Wvla', '-Wpointer-arith', ] - warning_c_flags = [ '-Wmissing-prototypes', '-Wdeclaration-after-statement', '-Wold-style-definition', ] - warning_cxx_flags = [ '-Wformat-nonliteral', ] @@ -194,13 +194,71 @@ gst_dep = dependency('gstreamer-1.0', version : gst_req, gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req, fallback : ['gstreamer', 'gst_base_dep']) gstpbutils_dep = dependency('gstreamer-pbutils-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'pbutils_dep']) + fallback : ['gst-plugins-base', 'pbutils_dep']) gstaudio_dep = dependency('gstreamer-audio-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'audio_dep']) + fallback : ['gst-plugins-base', 'audio_dep']) gsttag_dep = dependency('gstreamer-tag-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'tag_dep']) + fallback : ['gst-plugins-base', 'tag_dep']) gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'video_dep']) + fallback : ['gst-plugins-base', 'video_dep']) + +# GStreamer OpenGL +gstgl_dep = dependency('gstreamer-gl-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstgl_dep'], required: false) +gstglproto_dep = dependency('', required : false) +gstglx11_dep = dependency('', required : false) +gstglwayland_dep = dependency('', required : false) +gstglegl_dep = dependency('', required : false) + +have_gstgl = gstgl_dep.found() + +if have_gstgl + if gstgl_dep.type_name() == 'pkgconfig' + gst_gl_apis = gstgl_dep.get_pkgconfig_variable('gl_apis').split() + gst_gl_winsys = gstgl_dep.get_pkgconfig_variable('gl_winsys').split() + gst_gl_platforms = gstgl_dep.get_pkgconfig_variable('gl_platforms').split() + else + gstbase = subproject('gst-plugins-base') + gst_gl_apis = gstbase.get_variable('enabled_gl_apis') + gst_gl_winsys = gstbase.get_variable('enabled_gl_winsys') + gst_gl_platforms = gstbase.get_variable('enabled_gl_platforms') + endif + + message('GStreamer OpenGL window systems: @0@'.format(' '.join(gst_gl_winsys))) + message('GStreamer OpenGL platforms: @0@'.format(' '.join(gst_gl_platforms))) + message('GStreamer OpenGL apis: @0@'.format(' '.join(gst_gl_apis))) + + foreach ws : ['x11', 'wayland', 'android', 'cocoa', 'eagl', 'win32', 'dispmanx', 'viv_fb'] + set_variable('gst_gl_have_window_@0@'.format(ws), gst_gl_winsys.contains(ws)) + endforeach + + foreach p : ['glx', 'egl', 'cgl', 'eagl', 'wgl'] + set_variable('gst_gl_have_platform_@0@'.format(p), gst_gl_platforms.contains(p)) + endforeach + + foreach api : ['gl', 'gles2'] + set_variable('gst_gl_have_api_@0@'.format(api), gst_gl_apis.contains(api)) + endforeach + + gstglproto_dep = dependency('gstreamer-gl-prototypes-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglproto_dep'], required: true) + # Behind specific checks because meson fails at optional dependencies with a + # fallback to the same subproject. On the first failure, meson will never + # check the system again even if the fallback never existed. + # Last checked with meson 0.54.3 + if gst_gl_have_window_x11 + gstglx11_dep = dependency('gstreamer-gl-x11-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglx11_dep'], required: true) + endif + if gst_gl_have_window_wayland + gstglwayland_dep = dependency('gstreamer-gl-wayland-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglwayland_dep'], required: true) + endif + if gst_gl_have_platform_egl + gstglegl_dep = dependency('gstreamer-gl-egl-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglegl_dep'], required: true) + endif +endif libm = cc.find_library('m', required : false) glib_dep = dependency('glib-2.0', version : glib_req, fallback: ['glib', 'libglib_dep']) @@ -219,15 +277,18 @@ if cxx.has_argument ('-Wno-unused') add_project_arguments('-Wno-unused', language: 'cpp') endif -gst_clapper_args = ['-DHAVE_CONFIG_H'] configinc = include_directories('.') libsinc = include_directories('gst') python3 = import('python').find_installation() gnome = import('gnome') gir = find_program('g-ir-scanner', required : true) -build_gir = gir.found() -gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \ + +if not gir.found() + error('Clapper requires GI bindings to be compiled') +endif + +gir_init_section = ['--add-init-section=extern void gst_init(gint*,gchar**);' + \ 'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \ 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ diff --git a/src/playerBase.js b/src/playerBase.js index d3db2478..5939101e 100644 --- a/src/playerBase.js +++ b/src/playerBase.js @@ -14,21 +14,9 @@ class ClapperPlayerBase extends GstClapper.Clapper { _init() { - if(!Gst.is_initialized()) - Gst.init(null); - - const plugin = 'gtk4glsink'; - const gtk4glsink = Gst.ElementFactory.make(plugin, null); - - if(!gtk4glsink) { - debug(new Error( - `Could not load "${plugin}".` - + ' Do you have gstreamer-plugins-good-gtk4 installed?' - )); - } - + const gtk4plugin = new GstClapper.ClapperGtk4Plugin(); const glsinkbin = Gst.ElementFactory.make('glsinkbin', null); - glsinkbin.sink = gtk4glsink; + glsinkbin.sink = gtk4plugin.video_sink; const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher(); const renderer = new GstClapper.ClapperVideoOverlayVideoRenderer({ @@ -40,7 +28,7 @@ class ClapperPlayerBase extends GstClapper.Clapper video_renderer: renderer }); - this.widget = gtk4glsink.widget; + this.widget = gtk4plugin.video_sink.widget; this.widget.vexpand = true; this.widget.hexpand = true; From b487d1f2c1387ed50d61af19412b0208f15a3b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Fri, 29 Jan 2021 17:52:27 +0100 Subject: [PATCH 16/19] Gtk4Plugin: remove subtitles scaling Causes jitter (even crashes on i965) when resizing video and honestly I think that subtitles rendered at video size look better. --- lib/gst/clapper/gtk4/gstgtkglsink.c | 51 ----------------------------- lib/gst/clapper/gtk4/gstgtkglsink.h | 1 - 2 files changed, 52 deletions(-) diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.c b/lib/gst/clapper/gtk4/gstgtkglsink.c index e680c5a0..a6854f98 100644 --- a/lib/gst/clapper/gtk4/gstgtkglsink.c +++ b/lib/gst/clapper/gtk4/gstgtkglsink.c @@ -128,41 +128,9 @@ gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query) return res; } -static void -_size_changed_cb (GtkWidget * widget, gint width, - gint height, GstGtkGLSink * gtk_sink) -{ - gboolean reconfigure; - - GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); - - /* Ignore size changes before widget is negotiated - * we are going to queue a resize after negotiation */ - if (!base_widget->negotiated) - return; - - GST_OBJECT_LOCK (gtk_sink); - reconfigure = - (width != gtk_sink->display_width || height != gtk_sink->display_height); - gtk_sink->display_width = width; - gtk_sink->display_height = height; - GST_OBJECT_UNLOCK (gtk_sink); - - if (reconfigure) { - GST_DEBUG_OBJECT (gtk_sink, "Sending reconfigure event on sinkpad"); - gst_pad_push_event (GST_BASE_SINK (gtk_sink)->sinkpad, - gst_event_new_reconfigure ()); - } -} - static void destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink) { - if (gtk_sink->widget_resize_sig_handler) { - g_signal_handler_disconnect (widget, gtk_sink->widget_resize_sig_handler); - gtk_sink->widget_resize_sig_handler = 0; - } - if (gtk_sink->widget_destroy_sig_handler) { g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler); gtk_sink->widget_destroy_sig_handler = 0; @@ -182,13 +150,6 @@ gst_gtk_gl_sink_start (GstBaseSink * bsink) /* After this point, gtk_sink->widget will always be set */ gst_widget = GTK_GST_GL_WIDGET (base_sink->widget); - /* Track the allocation size */ - if (!gtk_sink->widget_resize_sig_handler) { - gtk_sink->widget_resize_sig_handler = - g_signal_connect (gst_widget, "resize", - G_CALLBACK (_size_changed_cb), gtk_sink); - } - if (!gtk_sink->widget_destroy_sig_handler) { gtk_sink->widget_destroy_sig_handler = g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb), @@ -226,12 +187,6 @@ gst_gtk_gl_sink_stop (GstBaseSink * bsink) GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); - if (gtk_sink->widget_resize_sig_handler) { - g_signal_handler_disconnect (base_sink->widget, - gtk_sink->widget_resize_sig_handler); - gtk_sink->widget_resize_sig_handler = 0; - } - if (gtk_sink->display) { gst_object_unref (gtk_sink->display); gtk_sink->display = NULL; @@ -371,12 +326,6 @@ gst_gtk_gl_sink_finalize (GObject * object) GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object); GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object); - if (gtk_sink->widget_resize_sig_handler) { - g_signal_handler_disconnect (base_sink->widget, - gtk_sink->widget_resize_sig_handler); - gtk_sink->widget_resize_sig_handler = 0; - } - if (gtk_sink->widget_destroy_sig_handler) { g_signal_handler_disconnect (base_sink->widget, gtk_sink->widget_destroy_sig_handler); diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.h b/lib/gst/clapper/gtk4/gstgtkglsink.h index 56595f23..9dbe8fc0 100644 --- a/lib/gst/clapper/gtk4/gstgtkglsink.h +++ b/lib/gst/clapper/gtk4/gstgtkglsink.h @@ -56,7 +56,6 @@ struct _GstGtkGLSink gint display_width; gint display_height; - gulong widget_resize_sig_handler; gulong widget_destroy_sig_handler; }; From 08cde45bad14520a4ae8c0f805fba58bc17bbf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Fri, 29 Jan 2021 18:18:41 +0100 Subject: [PATCH 17/19] Gtk4Plugin: add drawing black fixes from Flatpak patch --- lib/gst/clapper/gtk4/gstgtkbasesink.c | 18 +++++++++++++++ lib/gst/clapper/gtk4/gstgtkbasesink.h | 3 +++ lib/gst/clapper/gtk4/gtkgstbasewidget.c | 14 ++++++++++++ lib/gst/clapper/gtk4/gtkgstbasewidget.h | 1 + lib/gst/clapper/gtk4/gtkgstglwidget.c | 30 +++++++++++++++++-------- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.c b/lib/gst/clapper/gtk4/gstgtkbasesink.c index 1ebdc81b..f4f19671 100644 --- a/lib/gst/clapper/gtk4/gstgtkbasesink.c +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.c @@ -39,6 +39,7 @@ GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink); #define DEFAULT_PAR_N 0 #define DEFAULT_PAR_D 1 #define DEFAULT_IGNORE_ALPHA TRUE +#define DEFAULT_IGNORE_TEXTURES FALSE static void gst_gtk_base_sink_finalize (GObject * object); static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id, @@ -70,6 +71,7 @@ enum PROP_FORCE_ASPECT_RATIO, PROP_PIXEL_ASPECT_RATIO, PROP_IGNORE_ALPHA, + PROP_IGNORE_TEXTURES, }; #define gst_gtk_base_sink_parent_class parent_class @@ -123,6 +125,11 @@ gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass) DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); #endif + g_object_class_install_property (gobject_class, PROP_IGNORE_TEXTURES, + g_param_spec_boolean ("ignore-textures", "Ignore Textures", + "When enabled, textures will be ignored and not drawn", + DEFAULT_IGNORE_TEXTURES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gobject_class->finalize = gst_gtk_base_sink_finalize; gstelement_class->change_state = gst_gtk_base_sink_change_state; @@ -143,6 +150,7 @@ gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink) gtk_sink->par_n = DEFAULT_PAR_N; gtk_sink->par_d = DEFAULT_PAR_D; gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA; + gtk_sink->ignore_textures = DEFAULT_IGNORE_TEXTURES; } static void @@ -221,6 +229,10 @@ gst_gtk_base_sink_get_widget (GstGtkBaseSink * gtk_sink) "ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); #endif + gtk_sink->bind_ignore_textures = + g_object_bind_property (gtk_sink, "ignore-textures", gtk_sink->widget, + "ignore-textures", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + /* Take the floating ref, other wise the destruction of the container will * make this widget disappear possibly before we are done. */ gst_object_ref_sink (gtk_sink->widget); @@ -268,6 +280,9 @@ gst_gtk_base_sink_get_property (GObject * object, guint prop_id, case PROP_IGNORE_ALPHA: g_value_set_boolean (value, gtk_sink->ignore_alpha); break; + case PROP_IGNORE_TEXTURES: + g_value_set_boolean (value, gtk_sink->ignore_textures); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -291,6 +306,9 @@ gst_gtk_base_sink_set_property (GObject * object, guint prop_id, case PROP_IGNORE_ALPHA: gtk_sink->ignore_alpha = g_value_get_boolean (value); break; + case PROP_IGNORE_TEXTURES: + gtk_sink->ignore_textures = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.h b/lib/gst/clapper/gtk4/gstgtkbasesink.h index db0acb2c..2a8b4b15 100644 --- a/lib/gst/clapper/gtk4/gstgtkbasesink.h +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.h @@ -68,6 +68,9 @@ struct _GstGtkBaseSink gboolean ignore_alpha; GBinding *bind_ignore_alpha; + gboolean ignore_textures; + GBinding *bind_ignore_textures; + GtkWidget *window; gulong widget_destroy_id; gulong window_destroy_id; diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.c b/lib/gst/clapper/gtk4/gtkgstbasewidget.c index 374eb7f9..85f6dc20 100644 --- a/lib/gst/clapper/gtk4/gtkgstbasewidget.c +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.c @@ -34,6 +34,7 @@ GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget); #define DEFAULT_PAR_N 0 #define DEFAULT_PAR_D 1 #define DEFAULT_IGNORE_ALPHA TRUE +#define DEFAULT_IGNORE_TEXTURES FALSE enum { @@ -41,6 +42,7 @@ enum PROP_FORCE_ASPECT_RATIO, PROP_PIXEL_ASPECT_RATIO, PROP_IGNORE_ALPHA, + PROP_IGNORE_TEXTURES, }; static void @@ -108,6 +110,9 @@ gtk_gst_base_widget_set_property (GObject * object, guint prop_id, case PROP_IGNORE_ALPHA: gtk_widget->ignore_alpha = g_value_get_boolean (value); break; + case PROP_IGNORE_TEXTURES: + gtk_widget->ignore_textures = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -130,6 +135,9 @@ gtk_gst_base_widget_get_property (GObject * object, guint prop_id, case PROP_IGNORE_ALPHA: g_value_set_boolean (value, gtk_widget->ignore_alpha); break; + case PROP_IGNORE_TEXTURES: + g_value_set_boolean (value, gtk_widget->ignore_textures); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -460,6 +468,11 @@ gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass) "When enabled, alpha will be ignored and converted to black", DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_klass, PROP_IGNORE_TEXTURES, + g_param_spec_boolean ("ignore-textures", "Ignore Textures", + "When enabled, textures will be ignored and not drawn", + DEFAULT_IGNORE_TEXTURES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + #if defined(BUILD_FOR_GTK4) widget_klass->measure = gtk_gst_base_widget_measure; #else @@ -478,6 +491,7 @@ gtk_gst_base_widget_init (GtkGstBaseWidget * widget) widget->par_n = DEFAULT_PAR_N; widget->par_d = DEFAULT_PAR_D; widget->ignore_alpha = DEFAULT_IGNORE_ALPHA; + widget->ignore_textures = DEFAULT_IGNORE_TEXTURES; gst_video_info_init (&widget->v_info); gst_video_info_init (&widget->pending_v_info); diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.h b/lib/gst/clapper/gtk4/gtkgstbasewidget.h index e96d2480..7151b36c 100644 --- a/lib/gst/clapper/gtk4/gtkgstbasewidget.h +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.h @@ -50,6 +50,7 @@ struct _GtkGstBaseWidget gboolean force_aspect_ratio; gint par_n, par_d; gboolean ignore_alpha; + gboolean ignore_textures; gint display_width; gint display_height; diff --git a/lib/gst/clapper/gtk4/gtkgstglwidget.c b/lib/gst/clapper/gtk4/gtkgstglwidget.c index a9250676..d7d53748 100644 --- a/lib/gst/clapper/gtk4/gtkgstglwidget.c +++ b/lib/gst/clapper/gtk4/gtkgstglwidget.c @@ -62,7 +62,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _GtkGstGLWidgetPrivate { - gboolean initted; + gboolean initiated; GstGLDisplay *display; GdkGLContext *gdk_context; GstGLContext *other_context; @@ -159,7 +159,7 @@ gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget) priv->overlay_compositor = gst_gl_overlay_compositor_new (priv->other_context); - priv->initted = TRUE; + priv->initiated = TRUE; } static void @@ -173,7 +173,7 @@ _redraw_texture (GtkGstGLWidget * gst_widget, guint tex) GstVideoRectangle src, dst, result; gint widget_width, widget_height, widget_scale; - gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gl->ClearColor (0.0, 0.0, 0.0, 1.0); gl->Clear (GL_COLOR_BUFFER_BIT); widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget); @@ -220,11 +220,19 @@ _draw_black (GstGLContext * context) { const GstGLFuncs *gl = context->gl_vtable; - gst_gl_insert_debug_marker (context, "no buffer. rendering black"); - gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gst_gl_insert_debug_marker (context, "rendering black"); + gl->ClearColor (0.0, 0.0, 0.0, 1.0); gl->Clear (GL_COLOR_BUFFER_BIT); } +static inline void +_draw_black_with_gdk (GdkGLContext * gdk_context) +{ + GST_DEBUG ("rendering empty frame with gdk context %p", gdk_context); + glClearColor (0.0, 0.0, 0.0, 1.0); + glClear (GL_COLOR_BUFFER_BIT); +} + static gboolean gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context) { @@ -233,15 +241,19 @@ gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context) GTK_GST_BASE_WIDGET_LOCK (widget); - if (!priv->context || !priv->other_context) + /* Draw black with GDK context when priv is not available yet. + GTK calls render with GDK context already active. */ + if (!priv->context || !priv->other_context || base_widget->ignore_textures) { + _draw_black_with_gdk (context); goto done; + } gst_gl_context_activate (priv->other_context, TRUE); - if (!priv->initted) - gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget)); + if (!priv->initiated || !base_widget->negotiated) { + if (!priv->initiated) + gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget)); - if (!priv->initted || !base_widget->negotiated) { _draw_black (priv->other_context); goto done; } From bf04af23fe0e3b97c917f71fc51896e05e338ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Tue, 2 Feb 2021 13:55:55 +0100 Subject: [PATCH 18/19] Do a lock on a gtk_sink Same object is unlocked here. Keep consistency. --- lib/gst/clapper/gtk4/gstgtkbasesink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.c b/lib/gst/clapper/gtk4/gstgtkbasesink.c index f4f19671..1f35a9de 100644 --- a/lib/gst/clapper/gtk4/gstgtkbasesink.c +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.c @@ -557,7 +557,7 @@ gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf) gtk_sink = GST_GTK_BASE_SINK (vsink); - GST_OBJECT_LOCK (vsink); + GST_OBJECT_LOCK (gtk_sink); if (gtk_sink->widget == NULL) { GST_OBJECT_UNLOCK (gtk_sink); From 04ce5c50188e1e9a94d4bb1facce5c07af68393f Mon Sep 17 00:00:00 2001 From: SpiritCS Date: Wed, 3 Feb 2021 17:59:34 +0100 Subject: [PATCH 19/19] pkgs: arch: gstplayer update, minor refactor --- pkgs/arch/.SRCINFO | 26 ++++++++++++----------- pkgs/arch/PKGBUILD | 52 ++++++++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/pkgs/arch/.SRCINFO b/pkgs/arch/.SRCINFO index 5ba45246..dac13ef3 100644 --- a/pkgs/arch/.SRCINFO +++ b/pkgs/arch/.SRCINFO @@ -1,26 +1,28 @@ pkgbase = clapper-git - pkgdesc = A GNOME media player built using GJS and powered by GStreamer with OpenGL rendering. Can also be used as a pre-made widget for GTK apps. - pkgver = r117.e7e9b9c + pkgdesc = A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering. + pkgver = r393.bf04af2 pkgrel = 1 url = https://github.com/Rafostar/clapper arch = any license = GPL-3.0 makedepends = meson>=0.50 - makedepends = gjs makedepends = git - depends = gtk3>=3.19.4 - depends = hicolor-icon-theme + depends = gtk4 depends = gjs - depends = gst-plugins-base-libs - depends = gst-plugins-good - depends = gst-plugins-bad-libs>=1.16.0 - depends = gst-plugin-gtk - optdepends = gst-libav: Popular video decoders - optdepends = gstreamer-vaapi: Intel/AMD video acceleration + depends = glib2>=2.56.0 + depends = gobject-introspection + depends = wayland-protocols + depends = hicolor-icon-theme + depends = gstreamer>=1.18.0 + depends = gst-plugins-base-libs>=1.18.0 + depends = gst-plugins-good>=1.18.0 + depends = gst-plugins-bad-libs>=1.18.0 + optdepends = gst-libav>=1.18.0: Popular video decoders + optdepends = gstreamer-vaapi>=1.18.0: Intel/AMD video acceleration provides = clapper conflicts = clapper replaces = clapper - source = clapper::git+https://github.com/Rafostar/clapper.git#commit=e7e9b9c07d884c1d412b15f0069117ddc7d0e635 + source = clapper::git+https://github.com/Rafostar/clapper.git#branch=gstplayer md5sums = SKIP pkgname = clapper-git diff --git a/pkgs/arch/PKGBUILD b/pkgs/arch/PKGBUILD index ead3249a..ca8028f8 100644 --- a/pkgs/arch/PKGBUILD +++ b/pkgs/arch/PKGBUILD @@ -1,7 +1,7 @@ # # PKGBUILD file for package clapper # -# Copyright (C) 2020 sp1rit +# Copyright (C) 2020/21 sp1rit # Copyright (C) 2020 Rafostar # # This program is free software: you can redistribute it and/or modify @@ -19,48 +19,56 @@ # Maintainer: sp1rit -pkgname=clapper-git -pkgver=r117.e7e9b9c +_basename=clapper +pkgname="${_basename}-git" +pkgver=r393.bf04af2 pkgrel=1 -pkgdesc="A GNOME media player built using GJS and powered by GStreamer with OpenGL rendering. Can also be used as a pre-made widget for GTK apps." +pkgdesc="A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering." arch=(any) url="https://github.com/Rafostar/clapper" license=("GPL-3.0") depends=( - "gtk3>=3.19.4" - "hicolor-icon-theme" + "gtk4" "gjs" - "gst-plugins-base-libs" - "gst-plugins-good" - "gst-plugins-bad-libs>=1.16.0" - "gst-plugin-gtk" + "glib2>=2.56.0" # glib-2.0, gmodule-2.0, gio-2.0 + "gobject-introspection" # /usr/sbin/g-ir-scanner + "wayland-protocols" # gtk4 non-default runtime dep + "hicolor-icon-theme" + "gstreamer>=1.18.0" # gstreamer-1.0, gstreamer-base-1.0 + "gst-plugins-base-libs>=1.18.0" # gstreamer-pbutils-1.0, gstreamer-audio-1.0, gstreamer-tag-1.0, gstreamer-video-1.0, gstreamer-gl-1.0, gstreamer-gl-prototypes-1.0, gstreamer-gl-x11-1.0, gstreamer-gl-wayland-1.0, gstreamer-gl-egl-1.0, + "gst-plugins-good>=1.18.0" + "gst-plugins-bad-libs>=1.18.0" ) makedepends=( "meson>=0.50" - "gjs" "git" ) optdepends=( - "gst-libav: Popular video decoders" - "gstreamer-vaapi: Intel/AMD video acceleration" + "gst-libav>=1.18.0: Popular video decoders" + "gstreamer-vaapi>=1.18.0: Intel/AMD video acceleration" ) -source=("${pkgname%-git}::git+https://github.com/Rafostar/${pkgname%-git}.git#commit=e7e9b9c07d884c1d412b15f0069117ddc7d0e635") -provides=("${pkgname%-git}") -replaces=("${pkgname%-git}") -conflicts=("${pkgname%-git}") +source=("${_basename}::git+https://github.com/Rafostar/${_basename}.git#branch=gstplayer") +provides=("${_basename}") +replaces=("${_basename}") +conflicts=("${_basename}") md5sums=("SKIP") pkgver() { - cd "$srcdir"/"${pkgname%-git}" + cd "${srcdir}/${_basename}" printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" } +prepare() { + cd "${srcdir}/${_basename}" + arch-meson . _build +} + build() { - cd "$srcdir"/"${pkgname%-git}" - meson build/ --prefix=/usr + cd "${srcdir}/${_basename}" + ninja -C _build } package() { - cd "$srcdir"/"${pkgname%-git}" - DESTDIR="$pkgdir" meson install -C build/ + cd "${srcdir}/${_basename}" + DESTDIR="$pkgdir" meson install -C _build/ }