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' +)