diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index 2b085af229568..7811bd760fc34 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -3,14 +3,14 @@ name: CMake Build Matrix on: push: branches: - - master + - '*' pull_request: branches: - master env: - QT_VERSION: 5.15.1 + QT_VERSION: 6.6.1 CCACHE_VERSION: 3.7.7 CACHE_PARTITION: dev HOST_N_CORES: 2 @@ -74,7 +74,7 @@ jobs: env: TOOLCHAIN: "win64_msvc2019_64" DIR: "msvc2019_64" - shell: bash --noprofile --norc -eo pipefail -x {0} + shell: bash --noprofile --norc -eo pipefail -x {0} run: | py -m pip install -r Tools/qt/qt-downloader-requirements.txt QT_INSTALL_DIR="/c/Qt" @@ -89,7 +89,7 @@ jobs: id: qt_mac if: runner.os == 'macOS' shell: bash --noprofile --norc -eo pipefail -x {0} - env: + env: TOOLCHAIN: "clang_64" run: | pip3 install -r Tools/qt/qt-downloader-requirements.txt @@ -138,7 +138,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update && sudo apt-get install \ - ninja-build build-essential bison flex gperf libfontconfig1-dev libgl1-mesa-dev libglib2.0-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libhyphen-dev libicu-dev libjpeg-dev libpng-dev libqt5opengl5-dev libqt5sensors5-dev libqt5webchannel5-dev libsqlite3-dev libwebp-dev libwoff-dev libxcomposite-dev libxml2-dev libxrender-dev libxslt1-dev mesa-common-dev pkg-config python3 qtbase5-private-dev qtdeclarative5-private-dev qtpositioning5-dev ruby libqt5sql5-sqlite qtbase5-doc-html qttools5-dev-tools \ + ninja-build build-essential bison flex gperf libfontconfig1-dev libgl1-mesa-dev libglib2.0-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libhyphen-dev libicu-dev libjpeg-dev libpng-dev libqt6opengl6-dev libqt6sensors6-dev libqt6webchannel6-dev libsqlite3-dev libwebp-dev libwoff-dev libxcomposite-dev libxml2-dev libxrender-dev libxslt1-dev mesa-common-dev pkg-config python3 qt6-base-dev qt6-base-private-dev qt6-declarative-private-dev qt6-positioning-dev ruby libqt6sql6-sqlite qt6-base-dev-tools \ libtasn1-6-dev libgcrypt20-dev libunwind-dev libharfbuzz-dev - name: CMake version diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index ead44e1c6f6fd..f582455cf11f6 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -7,7 +7,7 @@ on: description: "Coin build number, e.g. 1600955993" required: true qt_version: - description: "Qt version used for building artifacts, e.g. 5.15.1" + description: "Qt version used for building artifacts, e.g. 6.6.1" required: true tag_name: description: "Tag name, e.g. qtwebkit-5.212.0-alpha5" diff --git a/Source/JavaScriptCore/PlatformQt.cmake b/Source/JavaScriptCore/PlatformQt.cmake index f94f928739ca2..87d49322332fe 100644 --- a/Source/JavaScriptCore/PlatformQt.cmake +++ b/Source/JavaScriptCore/PlatformQt.cmake @@ -15,11 +15,11 @@ list(APPEND JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS ) list(APPEND JavaScriptCore_SYSTEM_INCLUDE_DIRECTORIES - ${Qt5Core_INCLUDE_DIRS} + ${Qt6Core_INCLUDE_DIRS} ) list(APPEND JavaScriptCore_LIBRARIES - ${Qt5Core_LIBRARIES} + ${Qt6Core_LIBRARIES} ) if (QT_STATIC_BUILD) diff --git a/Source/PlatformQt.cmake b/Source/PlatformQt.cmake index c70afacb1e801..bc5baabcd069e 100644 --- a/Source/PlatformQt.cmake +++ b/Source/PlatformQt.cmake @@ -132,35 +132,35 @@ endmacro () set(_package_footer_template " ####### Expanded from QTWEBKIT_PACKAGE_FOOTER variable ####### -set(Qt5@MODULE_NAME@_LIBRARIES Qt5::@MODULE_NAME@) -set(Qt5@MODULE_NAME@_VERSION_STRING \${Qt5@MODULE_NAME@_VERSION}) -set(Qt5@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS \"\") -set(Qt5@MODULE_NAME@_PRIVATE_INCLUDE_DIRS \"\") # FIXME: Support private headers - -get_target_property(Qt5@MODULE_NAME@_INCLUDE_DIRS Qt5::@FULL_MODULE_NAME@ INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(Qt5@MODULE_NAME@_COMPILE_DEFINITIONS Qt5::@FULL_MODULE_NAME@ INTERFACE_COMPILE_DEFINITIONS) - -foreach (_module_dep \${_Qt5@MODULE_NAME@_MODULE_DEPENDENCIES}) - list(APPEND Qt5@MODULE_NAME@_INCLUDE_DIRS \${Qt5\${_module_dep}_INCLUDE_DIRS}) - list(APPEND Qt5@MODULE_NAME@_PRIVATE_INCLUDE_DIRS \${Qt5\${_module_dep}_PRIVATE_INCLUDE_DIRS}) - list(APPEND Qt5@MODULE_NAME@_DEFINITIONS \${Qt5\${_module_dep}_DEFINITIONS}) - list(APPEND Qt5@MODULE_NAME@_COMPILE_DEFINITIONS \${Qt5\${_module_dep}_COMPILE_DEFINITIONS}) - list(APPEND Qt5@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS \${Qt5\${_module_dep}_EXECUTABLE_COMPILE_FLAGS}) +set(Qt6@MODULE_NAME@_LIBRARIES Qt6::@MODULE_NAME@) +set(Qt6@MODULE_NAME@_VERSION_STRING \${Qt6@MODULE_NAME@_VERSION}) +set(Qt6@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS \"\") +set(Qt6@MODULE_NAME@_PRIVATE_INCLUDE_DIRS \"\") # FIXME: Support private headers + +get_target_property(Qt6@MODULE_NAME@_INCLUDE_DIRS Qt6::@FULL_MODULE_NAME@ INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(Qt6@MODULE_NAME@_COMPILE_DEFINITIONS Qt6::@FULL_MODULE_NAME@ INTERFACE_COMPILE_DEFINITIONS) + +foreach (_module_dep \${_Qt6@MODULE_NAME@_MODULE_DEPENDENCIES}) + list(APPEND Qt6@MODULE_NAME@_INCLUDE_DIRS \${Qt6\${_module_dep}_INCLUDE_DIRS}) + list(APPEND Qt6@MODULE_NAME@_PRIVATE_INCLUDE_DIRS \${Qt6\${_module_dep}_PRIVATE_INCLUDE_DIRS}) + list(APPEND Qt6@MODULE_NAME@_DEFINITIONS \${Qt6\${_module_dep}_DEFINITIONS}) + list(APPEND Qt6@MODULE_NAME@_COMPILE_DEFINITIONS \${Qt6\${_module_dep}_COMPILE_DEFINITIONS}) + list(APPEND Qt6@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS \${Qt6\${_module_dep}_EXECUTABLE_COMPILE_FLAGS}) endforeach () -list(REMOVE_DUPLICATES Qt5@MODULE_NAME@_INCLUDE_DIRS) -list(REMOVE_DUPLICATES Qt5@MODULE_NAME@_PRIVATE_INCLUDE_DIRS) -list(REMOVE_DUPLICATES Qt5@MODULE_NAME@_DEFINITIONS) -list(REMOVE_DUPLICATES Qt5@MODULE_NAME@_COMPILE_DEFINITIONS) -list(REMOVE_DUPLICATES Qt5@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS) +list(REMOVE_DUPLICATES Qt6@MODULE_NAME@_INCLUDE_DIRS) +list(REMOVE_DUPLICATES Qt6@MODULE_NAME@_PRIVATE_INCLUDE_DIRS) +list(REMOVE_DUPLICATES Qt6@MODULE_NAME@_DEFINITIONS) +list(REMOVE_DUPLICATES Qt6@MODULE_NAME@_COMPILE_DEFINITIONS) +list(REMOVE_DUPLICATES Qt6@MODULE_NAME@_EXECUTABLE_COMPILE_FLAGS) # Fixup order of configurations to match behavior of other Qt modules # See also https://bugreports.qt.io/browse/QTBUG-29186 -get_target_property(_configurations Qt5::@FULL_MODULE_NAME@ IMPORTED_CONFIGURATIONS) +get_target_property(_configurations Qt6::@FULL_MODULE_NAME@ IMPORTED_CONFIGURATIONS) list(FIND _configurations RELEASE _index) if (\${_index} GREATER -1) list(REMOVE_AT _configurations \${_index}) list(INSERT _configurations 0 RELEASE) - set_property(TARGET Qt5::@FULL_MODULE_NAME@ PROPERTY IMPORTED_CONFIGURATIONS \"\${_configurations}\") + set_property(TARGET Qt6::@FULL_MODULE_NAME@ PROPERTY IMPORTED_CONFIGURATIONS \"\${_configurations}\") endif () unset(_configurations) unset(_index) @@ -169,73 +169,73 @@ unset(_index) set(MODULE_NAME WebKit) set(FULL_MODULE_NAME WebKitLegacy) string(CONFIGURE ${_package_footer_template} QTWEBKIT_PACKAGE_FOOTER @ONLY) -ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Qt5WebKitConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitConfig.cmake" - INSTALL_DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKit" +ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Qt6WebKitConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitConfig.cmake" + INSTALL_DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKit" ) set(MODULE_NAME WebKitWidgets) set(FULL_MODULE_NAME WebKitWidgets) string(CONFIGURE ${_package_footer_template} QTWEBKIT_PACKAGE_FOOTER @ONLY) -ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Qt5WebKitWidgetsConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitWidgetsConfig.cmake" - INSTALL_DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKitWidgets" +ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Qt6WebKitWidgetsConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitWidgetsConfig.cmake" + INSTALL_DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKitWidgets" ) unset(MODULE_NAME) unset(FULL_MODULE_NAME) unset(QTWEBKIT_PACKAGE_FOOTER) -write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitConfigVersion.cmake" +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) -write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitWidgetsConfigVersion.cmake" +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitWidgetsConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitConfigVersion.cmake" - DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKit" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitConfigVersion.cmake" + DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKit" COMPONENT Data ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitWidgetsConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/Qt5WebKitWidgetsConfigVersion.cmake" - DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKitWidgets" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitWidgetsConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/Qt6WebKitWidgetsConfigVersion.cmake" + DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKitWidgets" COMPONENT Data ) # We need to install separate config files for debug and release, so use "Code" component install(EXPORT WebKitTargets FILE WebKitTargets.cmake - NAMESPACE Qt5:: - DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKit" + NAMESPACE Qt6:: + DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKit" COMPONENT Code ) -install(EXPORT Qt5WebKitWidgetsTargets - FILE Qt5WebKitWidgetsTargets.cmake - NAMESPACE Qt5:: - DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt5WebKitWidgets" +install(EXPORT Qt6WebKitWidgetsTargets + FILE Qt6WebKitWidgetsTargets.cmake + NAMESPACE Qt6:: + DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/Qt6WebKitWidgets" COMPONENT Code ) # Documentation -if (NOT TARGET Qt5::qdoc) - add_executable(Qt5::qdoc IMPORTED) +if (NOT TARGET Qt6::qdoc) + add_executable(Qt6::qdoc IMPORTED) query_qmake(QDOC_EXECUTABLE QT_INSTALL_BINS) set(QDOC_EXECUTABLE "${QDOC_EXECUTABLE}/qdoc") - set_target_properties(Qt5::qdoc PROPERTIES + set_target_properties(Qt6::qdoc PROPERTIES IMPORTED_LOCATION ${QDOC_EXECUTABLE} ) endif () -if (NOT TARGET Qt5::qhelpgenerator) - add_executable(Qt5::qhelpgenerator IMPORTED) +if (NOT TARGET Qt6::qhelpgenerator) + add_executable(Qt6::qhelpgenerator IMPORTED) query_qmake(QHELPGENERATOR_EXECUTABLE QT_INSTALL_BINS) set(QHELPGENERATOR_EXECUTABLE "${QHELPGENERATOR_EXECUTABLE}/qhelpgenerator") - set_target_properties(Qt5::qhelpgenerator PROPERTIES + set_target_properties(Qt6::qhelpgenerator PROPERTIES IMPORTED_LOCATION ${QHELPGENERATOR_EXECUTABLE} ) endif () @@ -272,13 +272,13 @@ set(EXPORT_VARS_COMMANDS add_custom_target(prepare_docs ${NEED_ALL} ${EXPORT_VARS_COMMANDS} - COMMAND Qt5::qdoc ${QDOC_CONFIG} -prepare -outputdir "${DOC_OUTPUT_DIR}/qtwebkit" -installdir ${DOC_INSTALL_DIR} -indexdir ${QT_INSTALL_DOCS} -no-link-errors + COMMAND Qt6::qdoc ${QDOC_CONFIG} -prepare -outputdir "${DOC_OUTPUT_DIR}/qtwebkit" -installdir ${DOC_INSTALL_DIR} -indexdir ${QT_INSTALL_DOCS} -no-link-errors VERBATIM ) add_custom_target(generate_docs ${NEED_ALL} ${EXPORT_VARS_COMMANDS} - COMMAND Qt5::qdoc ${QDOC_CONFIG} -generate -outputdir "${DOC_OUTPUT_DIR}/qtwebkit" -installdir ${DOC_INSTALL_DIR} -indexdir ${QT_INSTALL_DOCS} + COMMAND Qt6::qdoc ${QDOC_CONFIG} -generate -outputdir "${DOC_OUTPUT_DIR}/qtwebkit" -installdir ${DOC_INSTALL_DIR} -indexdir ${QT_INSTALL_DOCS} VERBATIM ) add_dependencies(generate_docs prepare_docs) @@ -287,7 +287,7 @@ add_custom_target(html_docs) add_dependencies(html_docs generate_docs) add_custom_target(qch_docs ${NEED_ALL} - COMMAND Qt5::qhelpgenerator "${DOC_OUTPUT_DIR}/qtwebkit/qtwebkit.qhp" -o "${DOC_OUTPUT_DIR}/qtwebkit.qch" + COMMAND Qt6::qhelpgenerator "${DOC_OUTPUT_DIR}/qtwebkit/qtwebkit.qhp" -o "${DOC_OUTPUT_DIR}/qtwebkit.qch" VERBATIM ) add_dependencies(qch_docs html_docs) diff --git a/Source/Qt5WebKitConfig.cmake.in b/Source/Qt5WebKitConfig.cmake.in deleted file mode 100644 index a50d0353fd588..0000000000000 --- a/Source/Qt5WebKitConfig.cmake.in +++ /dev/null @@ -1,13 +0,0 @@ -@PACKAGE_INIT@ -@QTWEBKIT_PACKAGE_INIT@ - -find_dependency_with_major_and_minor(Qt5Core @Qt5_VERSION_MAJOR@ @Qt5_VERSION_MINOR@) -find_dependency_with_major_and_minor(Qt5Gui @Qt5_VERSION_MAJOR@ @Qt5_VERSION_MINOR@) -find_dependency_with_major_and_minor(Qt5Network @Qt5_VERSION_MAJOR@ @Qt5_VERSION_MINOR@) - -include("${CMAKE_CURRENT_LIST_DIR}/WebKitTargets.cmake") - -set(_Qt5WebKit_MODULE_DEPENDENCIES "Gui;Network;Core") -set(Qt5WebKit_DEFINITIONS -DQT_WEBKIT_LIB) - -@QTWEBKIT_PACKAGE_FOOTER@ diff --git a/Source/Qt5WebKitWidgetsConfig.cmake.in b/Source/Qt5WebKitWidgetsConfig.cmake.in deleted file mode 100644 index ef1c42956f4c3..0000000000000 --- a/Source/Qt5WebKitWidgetsConfig.cmake.in +++ /dev/null @@ -1,12 +0,0 @@ -@PACKAGE_INIT@ -@QTWEBKIT_PACKAGE_INIT@ - -find_dependency(Qt5WebKit @PROJECT_VERSION_STRING@ EXACT) -find_dependency_with_major_and_minor(Qt5Widgets @Qt5_VERSION_MAJOR@ @Qt5_VERSION_MINOR@) - -include("${CMAKE_CURRENT_LIST_DIR}/Qt5WebKitWidgetsTargets.cmake") - -set(_Qt5WebKitWidgets_MODULE_DEPENDENCIES "WebKit;Widgets;Gui;Network;Core") -set(Qt5WebKitWidgets_DEFINITIONS -DQT_WEBKITWIDGETS_LIB) - -@QTWEBKIT_PACKAGE_FOOTER@ diff --git a/Source/Qt6WebKitConfig.cmake.in b/Source/Qt6WebKitConfig.cmake.in new file mode 100644 index 0000000000000..538523ae50794 --- /dev/null +++ b/Source/Qt6WebKitConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ +@QTWEBKIT_PACKAGE_INIT@ + +find_dependency_with_major_and_minor(Qt6Core @Qt6_VERSION_MAJOR@ @Qt6_VERSION_MINOR@) +find_dependency_with_major_and_minor(Qt6Gui @Qt6_VERSION_MAJOR@ @Qt6_VERSION_MINOR@) +find_dependency_with_major_and_minor(Qt6Network @Qt6_VERSION_MAJOR@ @Qt6_VERSION_MINOR@) + +include("${CMAKE_CURRENT_LIST_DIR}/WebKitTargets.cmake") + +set(_Qt6WebKit_MODULE_DEPENDENCIES "Gui;Network;Core") +set(Qt6WebKit_DEFINITIONS -DQT_WEBKIT_LIB) + +@QTWEBKIT_PACKAGE_FOOTER@ diff --git a/Source/Qt6WebKitWidgetsConfig.cmake.in b/Source/Qt6WebKitWidgetsConfig.cmake.in new file mode 100644 index 0000000000000..d0c040d45334d --- /dev/null +++ b/Source/Qt6WebKitWidgetsConfig.cmake.in @@ -0,0 +1,12 @@ +@PACKAGE_INIT@ +@QTWEBKIT_PACKAGE_INIT@ + +find_dependency(Qt6WebKit @PROJECT_VERSION_STRING@ EXACT) +find_dependency_with_major_and_minor(Qt6Widgets @Qt6_VERSION_MAJOR@ @Qt6_VERSION_MINOR@) + +include("${CMAKE_CURRENT_LIST_DIR}/Qt6WebKitWidgetsTargets.cmake") + +set(_Qt6WebKitWidgets_MODULE_DEPENDENCIES "WebKit;Widgets;Gui;Network;Core") +set(Qt6WebKitWidgets_DEFINITIONS -DQT_WEBKITWIDGETS_LIB) + +@QTWEBKIT_PACKAGE_FOOTER@ diff --git a/Source/WTF/wtf/FileSystem.h b/Source/WTF/wtf/FileSystem.h index 7eba22c610651..0010c853a7e3b 100644 --- a/Source/WTF/wtf/FileSystem.h +++ b/Source/WTF/wtf/FileSystem.h @@ -71,7 +71,9 @@ const PlatformFileHandle invalidPlatformFileHandle = -1; #endif // PlatformFileID -#if OS(WINDOWS) +#if USE(GLIB) && !OS(WINDOWS) && !PLATFORM(QT) +typedef CString PlatformFileID; +#elif OS(WINDOWS) typedef FILE_ID_128 PlatformFileID; #else typedef ino_t PlatformFileID; diff --git a/Source/WTF/wtf/Platform.h b/Source/WTF/wtf/Platform.h index 361d02dcf8d88..9bcd7b67b0484 100644 --- a/Source/WTF/wtf/Platform.h +++ b/Source/WTF/wtf/Platform.h @@ -168,7 +168,7 @@ #endif // QT_VERSION >= QT_VERSION_CHECK(5,8,0) #include -#if !QT_CONFIG(bearermanagement) +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) || !QT_CONFIG(bearermanagement) #ifndef QT_NO_BEARERMANAGEMENT #define QT_NO_BEARERMANAGEMENT #endif // QT_NO_BEARERMANAGEMENT diff --git a/Source/WTF/wtf/PlatformQt.cmake b/Source/WTF/wtf/PlatformQt.cmake index f0784a4576168..fb18c3b390084 100644 --- a/Source/WTF/wtf/PlatformQt.cmake +++ b/Source/WTF/wtf/PlatformQt.cmake @@ -55,11 +55,11 @@ if (USE_MACH_PORTS) endif() list(APPEND WTF_SYSTEM_INCLUDE_DIRECTORIES - ${Qt5Core_INCLUDE_DIRS} + ${Qt6Core_INCLUDE_DIRS} ) list(APPEND WTF_LIBRARIES - ${Qt5Core_LIBRARIES} + ${Qt6Core_LIBRARIES} Threads::Threads ) diff --git a/Source/WTF/wtf/qt/RunLoopQt.cpp b/Source/WTF/wtf/qt/RunLoopQt.cpp index e7695932e09c4..6de0c5d322d83 100644 --- a/Source/WTF/wtf/qt/RunLoopQt.cpp +++ b/Source/WTF/wtf/qt/RunLoopQt.cpp @@ -242,6 +242,12 @@ bool RunLoop::TimerBase::isActive() const return m_isActive; } +Seconds RunLoop::TimerBase::secondsUntilFire() const +{ + // FIXME: implement for WebKit + return 0_s; +} + #include "RunLoopQt.moc" } // namespace WTF diff --git a/Source/WTF/wtf/text/AtomString.h b/Source/WTF/wtf/text/AtomString.h index 8f2b6e11cfe8e..374d26acebefc 100644 --- a/Source/WTF/wtf/text/AtomString.h +++ b/Source/WTF/wtf/text/AtomString.h @@ -126,7 +126,7 @@ class AtomString final { #if PLATFORM(QT) WTF_EXPORT_PRIVATE AtomString(const QString&); - WTF_EXPORT_PRIVATE AtomString(const QStringRef&); + WTF_EXPORT_PRIVATE AtomString(QStringView); #endif #if OS(WINDOWS) @@ -160,7 +160,7 @@ static_assert(sizeof(AtomString) == sizeof(String), "AtomString and String must inline bool operator==(const AtomString& a, const AtomString& b) { return a.impl() == b.impl(); } inline bool operator==(const AtomString& a, ASCIILiteral b) { return WTF::equal(a.impl(), b); } -inline bool operator==(const AtomString& a, const Vector& b) { return a.impl() && equal(a.impl(), b.data(), b.size()); } +inline bool operator==(const AtomString& a, const Vector& b) { return a.impl() && equal(a.impl(), b.data(), b.size()); } inline bool operator==(const AtomString& a, const String& b) { return equal(a.impl(), b.impl()); } inline bool operator==(const String& a, const AtomString& b) { return equal(a.impl(), b.impl()); } inline bool operator==(const Vector& a, const AtomString& b) { return b == a; } diff --git a/Source/WTF/wtf/text/WTFString.h b/Source/WTF/wtf/text/WTFString.h index 2dc29c5daba59..509351b7351ad 100644 --- a/Source/WTF/wtf/text/WTFString.h +++ b/Source/WTF/wtf/text/WTFString.h @@ -38,6 +38,9 @@ #if PLATFORM(QT) QT_BEGIN_NAMESPACE class QString; +class QStringView; +class QLatin1String; +class QByteArrayView; QT_END_NAMESPACE #endif @@ -260,8 +263,15 @@ class String final { #if PLATFORM(QT) WTF_EXPORT_PRIVATE String(const QString&); - WTF_EXPORT_PRIVATE String(const QStringRef&); + WTF_EXPORT_PRIVATE String(QLatin1String); + WTF_EXPORT_PRIVATE String(QStringView); + WTF_EXPORT_PRIVATE String(QByteArrayView); WTF_EXPORT_PRIVATE operator QString() const; + + // String(QStringView) makes for an ambiguous constructor, so we need to make these explicit + ALWAYS_INLINE String(Vector characters) : String(characters.data(), characters.size()) {} + ALWAYS_INLINE String(Vector characters) : String(characters.data(), characters.size()) {} + ALWAYS_INLINE String(Vector characters) : String(characters.data(), characters.size()) {} #endif #if OS(WINDOWS) diff --git a/Source/WTF/wtf/text/qt/AtomStringQt.cpp b/Source/WTF/wtf/text/qt/AtomStringQt.cpp index a1ad6ff48238d..8edc6ca19a3c9 100644 --- a/Source/WTF/wtf/text/qt/AtomStringQt.cpp +++ b/Source/WTF/wtf/text/qt/AtomStringQt.cpp @@ -12,10 +12,10 @@ AtomString::AtomString(const QString& qstr) return; } -AtomString::AtomString(const QStringRef& ref) - : m_string(AtomStringImpl::add(reinterpret_cast_ptr(ref.unicode()), ref.length())) +AtomString::AtomString(QStringView view) + : m_string(AtomStringImpl::add(reinterpret_cast_ptr(view.constData()), view.length())) { - if (!ref.string()) + if (view.isNull()) return; } diff --git a/Source/WTF/wtf/text/qt/StringQt.cpp b/Source/WTF/wtf/text/qt/StringQt.cpp index 4c7392536f6e5..2b92aecd36972 100644 --- a/Source/WTF/wtf/text/qt/StringQt.cpp +++ b/Source/WTF/wtf/text/qt/StringQt.cpp @@ -20,12 +20,15 @@ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include +#include +#include +#include #include #include @@ -39,11 +42,25 @@ String::String(const QString& qstr) m_impl = StringImpl::create(reinterpret_cast_ptr(qstr.constData()), qstr.length()); } -String::String(const QStringRef& ref) +String::String(QLatin1String view) { - if (!ref.string()) + if (view.isNull()) return; - m_impl = StringImpl::create(reinterpret_cast_ptr(ref.unicode()), ref.length()); + m_impl = StringImpl::create(reinterpret_cast_ptr(view.data()), view.size()); +} + +String::String(QStringView view) +{ + if (view.isNull()) + return; + m_impl = StringImpl::create(reinterpret_cast_ptr(view.data()), view.length()); +} + +String::String(QByteArrayView view) +{ + if (view.isNull()) + return; + m_impl = StringImpl::create(reinterpret_cast_ptr(view.data()), view.length()); } String::operator QString() const diff --git a/Source/WebCore/PlatformQt.cmake b/Source/WebCore/PlatformQt.cmake index 416af44b4a25c..fcd531fc9e3f4 100644 --- a/Source/WebCore/PlatformQt.cmake +++ b/Source/WebCore/PlatformQt.cmake @@ -84,6 +84,9 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS platform/qt/QStyleFacade.h platform/qt/QStyleHelpers.h platform/qt/QWebPageClient.h + platform/qt/RenderThemeQt.h + platform/qt/RenderThemeQStyle.h + platform/qt/ScrollbarThemeQStyle.h platform/qt/ThirdPartyCookiesQt.h platform/qt/UserAgentQt.h @@ -260,7 +263,7 @@ endif () # Do it in the WebCore to support SHARED_CORE since WebKitWidgets won't load WebKitLegacy in that case. # This should match the opposite statement in WebKitLegacy/PlatformQt.cmake if (SHARED_CORE) - qt5_add_resources(WebCore_SOURCES + qt6_add_resources(WebCore_SOURCES WebCore.qrc ) @@ -272,18 +275,18 @@ if (SHARED_CORE) endif () endif () -# Note: Qt5Network_INCLUDE_DIRS includes Qt5Core_INCLUDE_DIRS +# Note: Qt6Network_INCLUDE_DIRS includes Qt6Core_INCLUDE_DIRS list(APPEND WebCore_SYSTEM_INCLUDE_DIRECTORIES ${HARFBUZZ_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS} ${HYPHEN_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR} ${LIBXSLT_INCLUDE_DIR} - ${Qt5Gui_INCLUDE_DIRS} - ${Qt5Gui_PRIVATE_INCLUDE_DIRS} - ${Qt5Network_INCLUDE_DIRS} - ${Qt5Network_PRIVATE_INCLUDE_DIRS} - ${Qt5Sensors_INCLUDE_DIRS} + ${Qt6Gui_INCLUDE_DIRS} + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} + ${Qt6Network_INCLUDE_DIRS} + ${Qt6Network_PRIVATE_INCLUDE_DIRS} + ${Qt6Sensors_INCLUDE_DIRS} ${SQLITE_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS} ) @@ -295,10 +298,10 @@ list(APPEND WebCore_LIBRARIES ${HYPHEN_LIBRARIES} ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES} - ${Qt5Core_LIBRARIES} - ${Qt5Gui_LIBRARIES} - ${Qt5Network_LIBRARIES} - ${Qt5Sensors_LIBRARIES} + ${Qt6Core_LIBRARIES} + ${Qt6Gui_LIBRARIES} + ${Qt6Network_LIBRARIES} + ${Qt6Sensors_LIBRARIES} ${SQLITE_LIBRARIES} ${X11_X11_LIB} ${ZLIB_LIBRARIES} @@ -333,14 +336,14 @@ if (ENABLE_OPENGL) platform/graphics/qt/QFramebufferPaintDevice.cpp ) - if (${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL GLESv2) + if (${Qt6Gui_OPENGL_IMPLEMENTATION} STREQUAL GLESv2) list(APPEND WebCore_SOURCES platform/graphics/opengl/Extensions3DOpenGLES.cpp platform/graphics/opengl/GraphicsContext3DOpenGLES.cpp ) list(APPEND WebCore_LIBRARIES - ${Qt5Gui_EGL_LIBRARIES} - ${Qt5Gui_OPENGL_LIBRARIES} + ${Qt6Gui_EGL_LIBRARIES} + ${Qt6Gui_OPENGL_LIBRARIES} ) else () list(APPEND WebCore_SOURCES @@ -392,7 +395,7 @@ if (USE_QT_MULTIMEDIA) platform/graphics/qt/MediaPlayerPrivateQt.cpp ) list(APPEND WebCore_LIBRARIES - ${Qt5Multimedia_LIBRARIES} + ${Qt6Multimedia_LIBRARIES} ) QTWEBKIT_GENERATE_MOC_FILES_H(WebCore platform/graphics/qt/MediaPlayerPrivateQt.h) endif () diff --git a/Source/WebCore/bridge/qt/qt_class.cpp b/Source/WebCore/bridge/qt/qt_class.cpp index 1d4796892acbf..b760fadd216b7 100644 --- a/Source/WebCore/bridge/qt/qt_class.cpp +++ b/Source/WebCore/bridge/qt/qt_class.cpp @@ -175,7 +175,7 @@ Field* QtClass::fieldNamed(PropertyName identifier, Instance* instance) const if (index >= 0) { const QMetaProperty prop = m_metaObject->property(index); - if (prop.isScriptable(obj)) { + if (prop.isScriptable()) { f = new QtField(prop); qtinst->m_fields.insert(name, f); return f; diff --git a/Source/WebCore/bridge/qt/qt_instance.cpp b/Source/WebCore/bridge/qt/qt_instance.cpp index 6a7c5cc9fb34a..fde72b17e2fe4 100644 --- a/Source/WebCore/bridge/qt/qt_instance.cpp +++ b/Source/WebCore/bridge/qt/qt_instance.cpp @@ -120,7 +120,7 @@ RefPtr QtInstance::getQtInstance(QObject* o, RootObject* rootObject, { JSLockHolder lock(WebCore::commonVM()); - foreach (QtInstance* instance, cachedInstances.values(o)) + Q_FOREACH (QtInstance* instance, cachedInstances.values(o)) if (instance->rootObject() == rootObject) { // The garbage collector removes instances, but it may happen that the wrapped // QObject dies before the gc kicks in. To handle that case we have to do an additional @@ -197,7 +197,7 @@ void QtInstance::getPropertyNames(JSGlobalObject* lexicalGlobalObject, PropertyN #ifndef QT_NO_PROPERTIES QList dynProps = obj->dynamicPropertyNames(); - foreach (const QByteArray& ba, dynProps) + Q_FOREACH (const QByteArray& ba, dynProps) array.add(Identifier::fromString(vm, String::fromUTF8(ba.constData()))); #endif @@ -255,7 +255,7 @@ JSValue QtInstance::stringValue(JSGlobalObject* lexicalGlobalObject) const && m.methodType() != QMetaMethod::Signal && m.parameterCount() == 0 && m.returnType() != QMetaType::Void) { - QVariant ret(m.returnType(), (void*)0); + QVariant ret(m.returnMetaType(), (void*)0); void * qargs[1]; qargs[0] = ret.data(); diff --git a/Source/WebCore/bridge/qt/qt_runtime.cpp b/Source/WebCore/bridge/qt/qt_runtime.cpp index 587a32fb0cb91..db066d5d5fdc3 100644 --- a/Source/WebCore/bridge/qt/qt_runtime.cpp +++ b/Source/WebCore/bridge/qt/qt_runtime.cpp @@ -561,9 +561,13 @@ QVariant convertValueToQVariant(JSContextRef context, JSValueRef value, QMetaTyp if (!dt.isValid()) dt = QDateTime::fromString(qstring, Qt::TextDate); if (!dt.isValid()) - dt = QDateTime::fromString(qstring, Qt::SystemLocaleDate); + dt = QLocale::system().toDateTime(qstring, QLocale::ShortFormat); if (!dt.isValid()) - dt = QDateTime::fromString(qstring, Qt::LocaleDate); + dt = QLocale::system().toDateTime(qstring, QLocale::LongFormat); + if (!dt.isValid()) + dt = QLocale().toDateTime(qstring, QLocale::ShortFormat); + if (!dt.isValid()) + dt = QLocale().toDateTime(qstring, QLocale::LongFormat); if (dt.isValid()) { ret = dt; dist = 2; @@ -573,9 +577,13 @@ QVariant convertValueToQVariant(JSContextRef context, JSValueRef value, QMetaTyp if (!dt.isValid()) dt = QDate::fromString(qstring, Qt::TextDate); if (!dt.isValid()) - dt = QDate::fromString(qstring, Qt::SystemLocaleDate); + dt = QLocale::system().toDate(qstring, QLocale::ShortFormat); + if (!dt.isValid()) + dt = QLocale::system().toDate(qstring, QLocale::LongFormat); if (!dt.isValid()) - dt = QDate::fromString(qstring, Qt::LocaleDate); + dt = QLocale().toDate(qstring, QLocale::ShortFormat); + if (!dt.isValid()) + dt = QLocale().toDate(qstring, QLocale::LongFormat); if (dt.isValid()) { ret = dt; dist = 3; @@ -585,9 +593,13 @@ QVariant convertValueToQVariant(JSContextRef context, JSValueRef value, QMetaTyp if (!dt.isValid()) dt = QTime::fromString(qstring, Qt::TextDate); if (!dt.isValid()) - dt = QTime::fromString(qstring, Qt::SystemLocaleDate); + dt = QLocale::system().toTime(qstring, QLocale::ShortFormat); + if (!dt.isValid()) + dt = QLocale::system().toTime(qstring, QLocale::LongFormat); if (!dt.isValid()) - dt = QTime::fromString(qstring, Qt::LocaleDate); + dt = QLocale().toTime(qstring, QLocale::ShortFormat); + if (!dt.isValid()) + dt = QLocale().toTime(qstring, QLocale::LongFormat); if (dt.isValid()) { ret = dt; dist = 3; @@ -1032,7 +1044,7 @@ static int findMethodIndex(JSContextRef context, QVector tooFewArgs; QVector conversionFailed; - foreach(int index, matchingIndices) { + Q_FOREACH(int index, matchingIndices) { QMetaMethod method = meta->method(index); QVector types; @@ -1102,7 +1114,7 @@ static int findMethodIndex(JSContextRef context, QtMethodMatchType retType = types[0]; if (retType.typeId() != QMetaType::Void) - args[0] = QVariant(retType.typeId(), (void *)0); // the return value + args[0] = QVariant(QMetaType(retType.typeId()), (void *)0); // the return value bool converted = true; int matchDistance = 0; @@ -1461,7 +1473,7 @@ JSValueRef QtRuntimeMethod::connectOrDisconnect(JSContextRef context, JSObjectRe // Now to find our previous connection object. QList conns = QtConnectionObject::connections.values(sender); - foreach (QtConnectionObject* conn, conns) { + Q_FOREACH (QtConnectionObject* conn, conns) { // Is this the right connection? if (!conn->match(context, sender, signalIndex, targetObject, targetFunction)) continue; @@ -1508,19 +1520,18 @@ QtConnectionObject::~QtConnectionObject() // Begin moc-generated code -- modify with care! Check "HAND EDIT" parts struct qt_meta_stringdata_QtConnectionObject_t { - QByteArrayData data[3]; - char stringdata[44]; + const uint offsetsAndSize[6]; + char stringdata0[44]; }; -#define QT_MOC_LITERAL(idx, ofs, len) { \ - Q_REFCOUNT_INITIALIZE_STATIC, len, 0, 0, \ - offsetof(qt_meta_stringdata_QtConnectionObject_t, stringdata) + ofs \ - - idx * sizeof(QByteArrayData) \ - } + +#define QT_MOC_LITERAL(ofs, len) \ + uint(offsetof(qt_meta_stringdata_QtConnectionObject_t, stringdata0) + ofs), len + static const qt_meta_stringdata_QtConnectionObject_t qt_meta_stringdata_QtConnectionObject = { { -QT_MOC_LITERAL(0, 0, 33), -QT_MOC_LITERAL(1, 34, 7), -QT_MOC_LITERAL(2, 42, 0) +QT_MOC_LITERAL(0, 33), +QT_MOC_LITERAL(34, 7), +QT_MOC_LITERAL(42, 0) }, "JSC::Bindings::QtConnectionObject\0" "execute\0\0" @@ -1561,10 +1572,23 @@ void QtConnectionObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, i } } -const QMetaObject QtConnectionObject::staticMetaObject = { - { &QObject::staticMetaObject, qt_meta_stringdata_QtConnectionObject.data, - qt_meta_data_QtConnectionObject, qt_static_metacall, 0, 0 } -}; +//const QMetaObject QtConnectionObject::staticMetaObject = { +// { &QObject::staticMetaObject, qt_meta_stringdata_QtConnectionObject.data, +// qt_meta_data_QtConnectionObject, qt_static_metacall, 0, 0 } +//}; + +const QMetaObject QtConnectionObject::staticMetaObject = { { + QMetaObject::SuperData::link(), + qt_meta_stringdata_QtConnectionObject.offsetsAndSize, + qt_meta_data_QtConnectionObject, + qt_static_metacall, + nullptr, +qt_incomplete_metaTypeArray +, QtPrivate::TypeAndForceComplete +>, + nullptr +} }; const QMetaObject *QtConnectionObject::metaObject() const { @@ -1574,7 +1598,7 @@ const QMetaObject *QtConnectionObject::metaObject() const void *QtConnectionObject::qt_metacast(const char *_clname) { if (!_clname) return 0; - if (!strcmp(_clname, qt_meta_stringdata_QtConnectionObject.stringdata)) + if (!strcmp(_clname, qt_meta_stringdata_QtConnectionObject.stringdata0)) return static_cast(const_cast(this)); return QObject::qt_metacast(_clname); } @@ -1606,7 +1630,7 @@ void QtConnectionObject::execute(void** argv) WTF::Vector args(argc); for (int i = 0; i < argc; i++) { - int argType = method.parameterType(i); + QMetaType argType = QMetaType(method.parameterType(i)); args[i] = convertQVariantToValue(m_context, m_rootObject.get(), QVariant(argType, argv[i+1]), ignoredException); } diff --git a/Source/WebCore/platform/LegacySchemeRegistry.h b/Source/WebCore/platform/LegacySchemeRegistry.h index 7370540139c8c..3801bcd0c9ca2 100644 --- a/Source/WebCore/platform/LegacySchemeRegistry.h +++ b/Source/WebCore/platform/LegacySchemeRegistry.h @@ -32,7 +32,7 @@ #if PLATFORM(QT) QT_BEGIN_NAMESPACE -class QStringList; +using QStringList = class QList; QT_END_NAMESPACE #endif diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.cpp b/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.cpp index 271ca069b52a9..6c3b61254c9af 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.cpp @@ -20,7 +20,7 @@ #include "config.h" -#if ENABLE(VIDEO) && USE(GSTREAMER) && USE(TEXTURE_MAPPER) +#if ENABLE(VIDEO) && USE(GSTREAMER) && USE(TEXTURE_MAPPER) && !PLATFORM(QT) #include "GStreamerVideoFrameHolder.h" #include "BitmapTexture.h" diff --git a/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.h b/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.h index 5d83e9ab4edfe..9dbf9a4c9c5e1 100644 --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.h +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerVideoFrameHolder.h @@ -20,7 +20,7 @@ #pragma once -#if ENABLE(VIDEO) && USE(GSTREAMER) && USE(TEXTURE_MAPPER) +#if ENABLE(VIDEO) && USE(GSTREAMER) && USE(TEXTURE_MAPPER) && !PLATFORM(QT) #include "MediaPlayerPrivateGStreamer.h" #include "TextureMapperPlatformLayerBuffer.h" diff --git a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp index fc4ea63325264..90f4d129831af 100644 --- a/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/ImageGStreamerQt.cpp @@ -31,8 +31,9 @@ namespace WebCore { ImageGStreamer::ImageGStreamer(GRefPtr&& sample) + : m_sample(WTFMove(sample)) { - GstCaps* caps = gst_sample_get_caps(sample.get()); + GstCaps* caps = gst_sample_get_caps(m_sample.get()); GstVideoInfo videoInfo; gst_video_info_init(&videoInfo); if (!gst_video_info_from_caps(&videoInfo, caps)) @@ -42,7 +43,7 @@ ImageGStreamer::ImageGStreamer(GRefPtr&& sample) // Right now the TextureMapper only supports chromas with one plane ASSERT(GST_VIDEO_INFO_N_PLANES(&videoInfo) == 1); - GstBuffer* buffer = gst_sample_get_buffer(sample.get()); + GstBuffer* buffer = gst_sample_get_buffer(m_sample.get()); if (UNLIKELY(!GST_IS_BUFFER(buffer))) return; diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index 270617dcb54db..ba922f1d8a29d 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -111,7 +111,7 @@ #include "GLVideoSinkGStreamer.h" #endif // USE(GSTREAMER_GL) -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) #include "BitmapTexture.h" #include "BitmapTexturePool.h" #include "GStreamerVideoFrameHolder.h" @@ -151,7 +151,7 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) , m_maxTimeLoadedAtLastDidLoadingProgress(MediaTime::zeroTime()) , m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::repaint) , m_pausedTimerHandler(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::pausedTimerFired) -#if USE(TEXTURE_MAPPER) && !USE(NICOSIA) +#if USE(TEXTURE_MAPPER) && !USE(NICOSIA) && !PLATFORM(QT) , m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxyGL)) #endif #if !RELEASE_LOG_DISABLED @@ -168,7 +168,7 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) #endif m_isPlayerShuttingDown.store(false); -#if USE(TEXTURE_MAPPER) && USE(NICOSIA) +#if USE(TEXTURE_MAPPER) && USE(NICOSIA) && !PLATFORM(QT) m_nicosiaLayer = Nicosia::ContentLayer::create(*this, [&]() -> Ref { #if USE(TEXTURE_MAPPER_DMABUF) @@ -219,7 +219,7 @@ MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() if (m_videoDecoderPlatform == GstVideoDecoderPlatform::Video4Linux) flushCurrentBuffer(); #endif -#if USE(TEXTURE_MAPPER) && USE(NICOSIA) +#if USE(TEXTURE_MAPPER) && USE(NICOSIA) && !PLATFORM(QT) m_nicosiaLayer->invalidateClient(); #endif @@ -3116,7 +3116,7 @@ void MediaPlayerPrivateGStreamer::configureVideoDecoder(GstElement* decoder) if (gstObjectHasProperty(decoder, "max-threads")) g_object_set(decoder, "max-threads", 2, nullptr); } -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) updateTextureMapperFlags(); #endif @@ -3229,7 +3229,7 @@ void MediaPlayerPrivateGStreamer::isLoopingChanged() ensureSeekFlags(); } -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) PlatformLayer* MediaPlayerPrivateGStreamer::platformLayer() const { #if USE(NICOSIA) @@ -3800,7 +3800,7 @@ void MediaPlayerPrivateGStreamer::triggerRepaint(GRefPtr&& sample) return; } -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) if (m_isUsingFallbackVideoSink) { Locker locker { m_drawLock }; auto proxyOperation = @@ -3975,13 +3975,13 @@ bool MediaPlayerPrivateGStreamer::setVideoSourceOrientation(ImageOrientation ori return false; m_videoSourceOrientation = orientation; -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) updateTextureMapperFlags(); #endif return true; } -#if USE(TEXTURE_MAPPER) +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) void MediaPlayerPrivateGStreamer::updateTextureMapperFlags() { switch (m_videoSourceOrientation.orientation()) { diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp.orig b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp.orig new file mode 100644 index 0000000000000..0a48bd1f70baa --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp.orig @@ -0,0 +1,4543 @@ +/* + * Copyright (C) 2007, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2007 Collabora Ltd. All rights reserved. + * Copyright (C) 2007 Alp Toker + * Copyright (C) 2009 Gustavo Noronha Silva + * Copyright (C) 2014 Cable Television Laboratories, Inc. + * Copyright (C) 2009, 2019 Igalia S.L + * Copyright (C) 2015, 2019 Metrological Group B.V. + * + * 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 + * aint with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "MediaPlayerPrivateGStreamer.h" +#include "VideoFrameGStreamer.h" + +#if ENABLE(VIDEO) && USE(GSTREAMER) + +#include "GraphicsContext.h" +#include "GStreamerAudioMixer.h" +#include "GStreamerCommon.h" +#include "GStreamerRegistryScanner.h" +#include "HTTPHeaderNames.h" +#include "ImageGStreamer.h" +#include "ImageOrientation.h" +#include "IntRect.h" +#include "Logging.h" +#include "MediaPlayer.h" +#include "MIMETypeRegistry.h" +#include "NotImplemented.h" +#include "OriginAccessPatterns.h" +#include "SecurityOrigin.h" +#include "TimeRanges.h" +#include "VideoSinkGStreamer.h" +#include "WebKitAudioSinkGStreamer.h" +#include "WebKitWebSourceGStreamer.h" +#include "AudioTrackPrivateGStreamer.h" +#include "InbandMetadataTextTrackPrivateGStreamer.h" +#include "InbandTextTrackPrivateGStreamer.h" +#include "TextCombinerGStreamer.h" +#include "TextSinkGStreamer.h" +#include "VideoFrameMetadataGStreamer.h" +#include "VideoTrackPrivateGStreamer.h" + +#if ENABLE(MEDIA_STREAM) +#include "GStreamerMediaStreamSource.h" +#include "MediaStreamPrivate.h" +#endif + +#if ENABLE(MEDIA_SOURCE) +#include "MediaSource.h" +#include "WebKitMediaSourceGStreamer.h" +#endif + +#if ENABLE(ENCRYPTED_MEDIA) +#include "CDMInstance.h" +#include "GStreamerEMEUtilities.h" +#include "SharedBuffer.h" +#include "WebKitCommonEncryptionDecryptorGStreamer.h" +#endif + +#if ENABLE(WEB_AUDIO) +#include "AudioSourceProviderGStreamer.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE(GSTREAMER_MPEGTS) +#define GST_USE_UNSTABLE_API +#include +#undef GST_USE_UNSTABLE_API +#endif // ENABLE(VIDEO) && USE(GSTREAMER_MPEGTS) + +#if USE(GSTREAMER_GL) +#include "GLVideoSinkGStreamer.h" +#endif // USE(GSTREAMER_GL) + +#if USE(TEXTURE_MAPPER) && !PLATFORM(QT) +#include "BitmapTexture.h" +#include "BitmapTexturePool.h" +#include "GStreamerVideoFrameHolder.h" +#include "TextureMapperPlatformLayerBuffer.h" +#include "TextureMapperPlatformLayerProxyGL.h" +#endif // USE(TEXTURE_MAPPER) + +#if USE(TEXTURE_MAPPER_DMABUF) +#include "DMABufFormat.h" +#include "DMABufObject.h" +#include "DMABufVideoSinkGStreamer.h" +#include "GBMBufferSwapchain.h" +#include "TextureMapperPlatformLayerProxyDMABuf.h" +#include +#include +#endif // USE(TEXTURE_MAPPER_DMABUF) + +GST_DEBUG_CATEGORY(webkit_media_player_debug); +#define GST_CAT_DEFAULT webkit_media_player_debug + +namespace WebCore { +using namespace std; + +#if USE(GSTREAMER_HOLEPUNCH) +static const FloatSize s_holePunchDefaultFrameSize(1280, 720); +#endif + +MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) + : m_notifier(MainThreadNotifier::create()) + , m_player(player) + , m_referrer(player->referrer()) + , m_cachedDuration(MediaTime::invalidTime()) + , m_timeOfOverlappingSeek(MediaTime::invalidTime()) + , m_fillTimer(*this, &MediaPlayerPrivateGStreamer::fillTimerFired) + , m_maxTimeLoaded(MediaTime::zeroTime()) + , m_preload(player->preload()) + , m_maxTimeLoadedAtLastDidLoadingProgress(MediaTime::zeroTime()) + , m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::repaint) +<<<<<<< HEAD + , m_readyTimerHandler(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::readyTimerFired) +#if USE(TEXTURE_MAPPER) && !USE(NICOSIA) && !PLATFORM(QT) +======= + , m_pausedTimerHandler(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::pausedTimerFired) +#if USE(TEXTURE_MAPPER) && !USE(NICOSIA) +>>>>>>> merge-upstream-2023-12-18 + , m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxyGL)) +#endif +#if !RELEASE_LOG_DISABLED + , m_logger(player->mediaPlayerLogger()) + , m_logIdentifier(player->mediaPlayerLogIdentifier()) +#endif +#if USE(TEXTURE_MAPPER_DMABUF) + , m_swapchain(adoptRef(new GBMBufferSwapchain(GBMBufferSwapchain::BufferSwapchainSize::Eight))) +#endif + , m_loader(player->createResourceLoader()) +{ +#if USE(GLIB) && !PLATFORM(QT) + m_pausedTimerHandler.setPriority(G_PRIORITY_DEFAULT_IDLE); +#endif + m_isPlayerShuttingDown.store(false); + +#if USE(TEXTURE_MAPPER) && USE(NICOSIA) && !PLATFORM(QT) + m_nicosiaLayer = Nicosia::ContentLayer::create(*this, + [&]() -> Ref { +#if USE(TEXTURE_MAPPER_DMABUF) + if (webKitDMABufVideoSinkIsEnabled() && webKitDMABufVideoSinkProbePlatform()) + return adoptRef(*new TextureMapperPlatformLayerProxyDMABuf); +#endif + return adoptRef(*new TextureMapperPlatformLayerProxyGL); + }()); +#endif + + ensureGStreamerInitialized(); + m_audioSink = createAudioSink(); + ensureSeekFlags(); +} + +MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() +{ + GST_DEBUG_OBJECT(pipeline(), "Disposing player"); + m_isPlayerShuttingDown.store(true); + + m_sinkTaskQueue.startAborting(); + + for (auto& track : m_audioTracks.values()) + track->disconnect(); + + for (auto& track : m_textTracks.values()) + track->disconnect(); + + for (auto& track : m_videoTracks.values()) + track->disconnect(); + + if (m_fillTimer.isActive()) + m_fillTimer.stop(); + + m_pausedTimerHandler.stop(); + + if (m_videoSink) { + GRefPtr videoSinkPad = adoptGRef(gst_element_get_static_pad(m_videoSink.get(), "sink")); + g_signal_handlers_disconnect_matched(videoSinkPad.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); + } + + if (m_pipeline) { + disconnectSimpleBusMessageCallback(m_pipeline.get()); + g_signal_handlers_disconnect_matched(m_pipeline.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); + } + +#if USE(GSTREAMER_GL) + if (m_videoDecoderPlatform == GstVideoDecoderPlatform::Video4Linux) + flushCurrentBuffer(); +#endif +#if USE(TEXTURE_MAPPER) && USE(NICOSIA) && !PLATFORM(QT) + m_nicosiaLayer->invalidateClient(); +#endif + + if (m_videoSink) + g_signal_handlers_disconnect_matched(m_videoSink.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); + + if (m_volumeElement) + g_signal_handlers_disconnect_matched(m_volumeElement.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); + + // This will release the GStreamer thread from m_drawCondition in non AC mode in case there's an ongoing triggerRepaint call + // waiting there, and ensure that any triggerRepaint call reaching the lock won't wait on m_drawCondition. + cancelRepaint(true); + +#if ENABLE(ENCRYPTED_MEDIA) + { + Locker cdmAttachmentLocker { m_cdmAttachmentLock }; + m_cdmAttachmentCondition.notifyAll(); + } +#endif + + // The change to GST_STATE_NULL state is always synchronous. So after this gets executed we don't need to worry + // about handlers running in the GStreamer thread. + if (m_pipeline) + gst_element_set_state(m_pipeline.get(), GST_STATE_NULL); + + m_player = nullptr; + m_notifier->invalidate(); +} + +bool MediaPlayerPrivateGStreamer::isAvailable() +{ + return true; +} + +class MediaPlayerFactoryGStreamer final : public MediaPlayerFactory { +private: + MediaPlayerEnums::MediaEngineIdentifier identifier() const final { return MediaPlayerEnums::MediaEngineIdentifier::GStreamer; }; + + Ref createMediaEnginePlayer(MediaPlayer* player) const final + { + return adoptRef(*new MediaPlayerPrivateGStreamer(player)); + } + + void getSupportedTypes(HashSet& types) const final + { + return MediaPlayerPrivateGStreamer::getSupportedTypes(types); + } + + MediaPlayer::SupportsType supportsTypeAndCodecs(const MediaEngineSupportParameters& parameters) const final + { + return MediaPlayerPrivateGStreamer::supportsType(parameters); + } + + bool supportsKeySystem(const String& keySystem, const String& mimeType) const final + { + return MediaPlayerPrivateGStreamer::supportsKeySystem(keySystem, mimeType); + } +}; + +void MediaPlayerPrivateGStreamer::registerMediaEngine(MediaEngineRegistrar registrar) +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, [] { + GST_DEBUG_CATEGORY_INIT(webkit_media_player_debug, "webkitmediaplayer", 0, "WebKit media player"); + }); + registrar(makeUnique()); +} + +void MediaPlayerPrivateGStreamer::load(const String& urlString) +{ + URL url { urlString }; + if (url.protocolIsAbout()) { + loadingFailed(MediaPlayer::NetworkState::FormatError, MediaPlayer::ReadyState::HaveNothing, true); + return; + } + + if (!ensureGStreamerInitialized()) { + loadingFailed(MediaPlayer::NetworkState::FormatError, MediaPlayer::ReadyState::HaveNothing, true); + return; + } + + RefPtr player = m_player.get(); + if (!player) { + loadingFailed(MediaPlayer::NetworkState::FormatError, MediaPlayer::ReadyState::HaveNothing, true); + return; + } + + registerWebKitGStreamerElements(); + + if (!m_pipeline) + createGSTPlayBin(url); + syncOnClock(true); + if (m_fillTimer.isActive()) + m_fillTimer.stop(); + + ASSERT(m_pipeline); + setVisibleInViewport(player->isVisibleInViewport()); + setPlaybinURL(url); + + GST_DEBUG_OBJECT(pipeline(), "preload: %s", convertEnumerationToString(m_preload).utf8().data()); + if (m_preload == MediaPlayer::Preload::None && !isMediaSource()) { + GST_INFO_OBJECT(pipeline(), "Delaying load."); + m_isDelayingLoad = true; + } + + // Reset network and ready states. Those will be set properly once + // the pipeline pre-rolled. + m_networkState = MediaPlayer::NetworkState::Loading; + player->networkStateChanged(); + m_readyState = MediaPlayer::ReadyState::HaveNothing; + player->readyStateChanged(); + m_areVolumeAndMuteInitialized = false; + + if (!m_isDelayingLoad) + commitLoad(); +} + +#if ENABLE(MEDIA_SOURCE) +void MediaPlayerPrivateGStreamer::load(const URL&, const ContentType&, MediaSourcePrivateClient&) +{ + // Properly fail so the global MediaPlayer tries to fallback to the next MediaPlayerPrivate. + m_networkState = MediaPlayer::NetworkState::FormatError; + if (RefPtr player = m_player.get()) + player->networkStateChanged(); +} +#endif + +#if ENABLE(MEDIA_STREAM) +void MediaPlayerPrivateGStreamer::load(MediaStreamPrivate& stream) +{ + m_streamPrivate = &stream; + load(makeString("mediastream://", stream.id())); + syncOnClock(false); + + if (RefPtr player = m_player.get()) + player->play(); +} +#endif + +void MediaPlayerPrivateGStreamer::cancelLoad() +{ + if (m_networkState < MediaPlayer::NetworkState::Loading || m_networkState == MediaPlayer::NetworkState::Loaded) + return; + + if (m_pipeline) + changePipelineState(GST_STATE_READY); +} + +void MediaPlayerPrivateGStreamer::prepareToPlay() +{ + GST_DEBUG_OBJECT(pipeline(), "Prepare to play"); + m_preload = MediaPlayer::Preload::Auto; + if (m_isDelayingLoad) { + m_isDelayingLoad = false; + commitLoad(); + } +} + +void MediaPlayerPrivateGStreamer::play() +{ + if (isMediaStreamPlayer()) { + m_pausedTime = MediaTime::invalidTime(); + if (m_startTime.isInvalid()) + m_startTime = MediaTime::createWithDouble(MonotonicTime::now().secondsSinceEpoch().value()); + } + + if (!m_playbackRate) { + if (m_playbackRatePausedState == PlaybackRatePausedState::ManuallyPaused) + m_playbackRatePausedState = PlaybackRatePausedState::RatePaused; + return; + } + + if (changePipelineState(GST_STATE_PLAYING)) { + m_isEndReached = false; + m_isDelayingLoad = false; + m_preload = MediaPlayer::Preload::Auto; + updateDownloadBufferingFlag(); + GST_INFO_OBJECT(pipeline(), "Play"); + RefPtr player = m_player.get(); + if (player && player->isLooping()) { + GST_DEBUG_OBJECT(pipeline(), "Scheduling initial SEGMENT seek"); + doSeek(SeekTarget { playbackPosition() }, m_playbackRate); + } + } else + loadingFailed(MediaPlayer::NetworkState::Empty); +} + +void MediaPlayerPrivateGStreamer::pause() +{ + if (isMediaStreamPlayer()) + m_pausedTime = currentMediaTime(); + + m_playbackRatePausedState = PlaybackRatePausedState::ManuallyPaused; + GstState currentState, pendingState; + gst_element_get_state(m_pipeline.get(), ¤tState, &pendingState, 0); + if (currentState < GST_STATE_PAUSED && pendingState <= GST_STATE_PAUSED) + return; + + if (changePipelineState(GST_STATE_PAUSED)) + GST_INFO_OBJECT(pipeline(), "Pause"); + else + loadingFailed(MediaPlayer::NetworkState::Empty); +} + +bool MediaPlayerPrivateGStreamer::paused() const +{ + if (!m_pipeline) + return true; + + if (m_isEndReached) { + GST_DEBUG_OBJECT(pipeline(), "Ignoring pause at EOS"); + return true; + } + + if (m_playbackRatePausedState == PlaybackRatePausedState::RatePaused + || m_playbackRatePausedState == PlaybackRatePausedState::ShouldMoveToPlaying) { + GST_DEBUG_OBJECT(pipeline(), "Playback rate is 0, simulating PAUSED state"); + return false; + } + + GstState state, pending; + auto stateChange = gst_element_get_state(m_pipeline.get(), &state, &pending, 0); + bool paused = state <= GST_STATE_PAUSED || (stateChange == GST_STATE_CHANGE_ASYNC && pending == GST_STATE_PAUSED); + GST_LOG_OBJECT(pipeline(), "Paused: %s (state %s, pending %s, state change %s)", boolForPrinting(paused), + gst_element_state_get_name(state), gst_element_state_get_name(pending), gst_element_state_change_return_get_name(stateChange)); + return paused; +} + +bool MediaPlayerPrivateGStreamer::doSeek(const SeekTarget& target, float rate) +{ + RefPtr player = m_player.get(); + + // Default values for rate >= 0. + MediaTime startTime = target.time, endTime = MediaTime::invalidTime(); + + if (rate < 0) { + startTime = MediaTime::zeroTime(); + // If we are at beginning of media, start from the end to avoid immediate EOS. + endTime = target.time <= MediaTime::zeroTime() ? durationMediaTime() : target.time; + } + + if (!rate) + rate = 1.0; + + if (m_hasWebKitWebSrcSentEOS && m_downloadBuffer) { + GST_DEBUG_OBJECT(pipeline(), "Setting high-percent=0 on GstDownloadBuffer to force 100%% buffered reporting"); + g_object_set(m_downloadBuffer.get(), "high-percent", 0, nullptr); + } + + if (paused() && !m_isEndReached && player && player->isLooping()) { + GST_DEBUG_OBJECT(pipeline(), "Segment non-flushing seek attempt not supported on a paused pipeline, enabling flush"); + m_seekFlags = static_cast((m_seekFlags | GST_SEEK_FLAG_FLUSH) & ~GST_SEEK_FLAG_SEGMENT); + } + + if (rate && player && player->isLooping() && startTime >= durationMediaTime()) { + didEnd(); + return true; + } + + auto seekStart = toGstClockTime(startTime); + auto seekStop = toGstClockTime(endTime); + GST_DEBUG_OBJECT(pipeline(), "[Seek] Performing actual seek to %" GST_TIMEP_FORMAT " (endTime: %" GST_TIMEP_FORMAT ") at rate %f", &seekStart, &seekStop, rate); + return gst_element_seek(m_pipeline.get(), rate, GST_FORMAT_TIME, m_seekFlags, GST_SEEK_TYPE_SET, seekStart, GST_SEEK_TYPE_SET, seekStop); +} + +void MediaPlayerPrivateGStreamer::seekToTarget(const SeekTarget& inTarget) +{ + if (!m_pipeline || m_didErrorOccur || isMediaStreamPlayer()) + return; + + GST_INFO_OBJECT(pipeline(), "[Seek] seek attempt to %s", toString(inTarget.time).utf8().data()); + + // Avoid useless seeking. + if (inTarget.time == currentMediaTime()) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] Already at requested position. Aborting."); + timeChanged(inTarget.time); + return; + } + + if (m_isLiveStream.value_or(false)) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] Live stream seek unhandled"); + return; + } + + RefPtr player = m_player.get(); + if (!player) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] m_player is nullptr"); + return; + } + + auto target = inTarget; + target.time = std::min(inTarget.time, durationMediaTime()); + GST_INFO_OBJECT(pipeline(), "[Seek] seeking to %s", toString(target.time).utf8().data()); + + if (m_isSeeking) { + m_timeOfOverlappingSeek = target.time; + if (m_isSeekPending) { + m_seekTarget = target; + return; + } + } + + GstState state; + GstStateChangeReturn getStateResult = gst_element_get_state(m_pipeline.get(), &state, nullptr, 0); + if (getStateResult == GST_STATE_CHANGE_FAILURE || getStateResult == GST_STATE_CHANGE_NO_PREROLL) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] cannot seek, current state change is %s", gst_element_state_change_return_get_name(getStateResult)); + return; + } + + if (player->isLooping() && isSeamlessSeekingEnabled() && state > GST_STATE_PAUSED) { + // Segment seeking is synchronous, the pipeline state has not changed, no flush is done. + GST_DEBUG_OBJECT(pipeline(), "Performing segment seek"); + m_isSeeking = true; + if (!doSeek(target, player->rate())) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] seeking to %s failed", toString(target.time).utf8().data()); + return; + } + m_isEndReached = false; + m_isSeeking = false; + m_cachedPosition = MediaTime::zeroTime(); + timeChanged(target.time); + return; + } + + if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED || m_isEndReached) { + m_isSeekPending = true; + if (m_isEndReached && (!player->isLooping() || !isSeamlessSeekingEnabled())) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] reset pipeline"); + m_shouldResetPipeline = true; + if (!changePipelineState(GST_STATE_PAUSED)) + loadingFailed(MediaPlayer::NetworkState::Empty); + } + } else { + // We can seek now. + if (!doSeek(target, player->rate())) { + GST_DEBUG_OBJECT(pipeline(), "[Seek] seeking to %s failed", toString(target.time).utf8().data()); + return; + } + } + + m_isSeeking = true; + m_seekTarget = target; + m_isEndReached = false; +} + +void MediaPlayerPrivateGStreamer::updatePlaybackRate() +{ + if (isMediaStreamPlayer() || !m_isChangingRate) + return; + + GST_INFO_OBJECT(pipeline(), "Set playback rate to %f", m_playbackRate); + + // Mute the sound if the playback rate is negative or too extreme and audio pitch is not adjusted. + bool mute = m_playbackRate <= 0 || (!m_shouldPreservePitch && (m_playbackRate < 0.8 || m_playbackRate > 2)); + + GST_INFO_OBJECT(pipeline(), mute ? "Need to mute audio" : "Do not need to mute audio"); + + if (m_lastPlaybackRate != m_playbackRate) { + if (doSeek(SeekTarget { playbackPosition() }, m_playbackRate)) { + g_object_set(m_pipeline.get(), "mute", mute, nullptr); + m_lastPlaybackRate = m_playbackRate; + } else { + GST_ERROR_OBJECT(pipeline(), "Set rate to %f failed", m_playbackRate); + m_playbackRate = m_lastPlaybackRate; + } + } + + m_isChangingRate = false; + if (RefPtr player = m_player.get()) + player->rateChanged(); +} + +MediaTime MediaPlayerPrivateGStreamer::durationMediaTime() const +{ + if (isMediaStreamPlayer()) + return MediaTime::positiveInfiniteTime(); + + GST_TRACE_OBJECT(pipeline(), "Cached duration: %s", m_cachedDuration.toString().utf8().data()); + if (m_cachedDuration.isValid()) + return m_cachedDuration; + + MediaTime duration = platformDuration(); + if (!duration || duration.isInvalid()) + return MediaTime::zeroTime(); + + m_cachedDuration = duration; + + return m_cachedDuration; +} + +MediaTime MediaPlayerPrivateGStreamer::currentMediaTime() const +{ + if (isMediaStreamPlayer()) { + if (m_pausedTime) + return m_pausedTime; + + return MediaTime::createWithDouble(MonotonicTime::now().secondsSinceEpoch().value()) - m_startTime; + } + + if (!m_pipeline || m_didErrorOccur) + return MediaTime::invalidTime(); + + GST_TRACE_OBJECT(pipeline(), "seeking: %s, seekTarget: %s", boolForPrinting(m_isSeeking), m_seekTarget.toString().utf8().data()); + if (m_isSeeking) + return m_seekTarget.time; + + return playbackPosition(); +} + +void MediaPlayerPrivateGStreamer::setRate(float rate) +{ + RefPtr player = m_player.get(); + + float rateClamped = clampTo(rate, -20.0, 20.0); + if (rateClamped != rate) + GST_WARNING_OBJECT(pipeline(), "Clamping original rate (%f) to [-20, 20] (%f), higher rates cause crashes", rate, rateClamped); + + GST_DEBUG_OBJECT(pipeline(), "Setting playback rate to %f", rateClamped); + // Avoid useless playback rate update. + if (m_playbackRate == rateClamped) { + // And make sure that upper layers were notified if rate was set. + + if (!m_isChangingRate && player && player->rate() != m_playbackRate) + player->rateChanged(); + return; + } + + if (m_isLiveStream.value_or(false)) { + // Notify upper layers that we cannot handle passed rate. + m_isChangingRate = false; + if (player) + player->rateChanged(); + return; + } + + m_playbackRate = rateClamped; + m_isChangingRate = true; + + if (!rateClamped) { + m_isChangingRate = false; + if (m_playbackRatePausedState == PlaybackRatePausedState::Playing || m_playbackRatePausedState == PlaybackRatePausedState::ShouldMoveToPlaying) { + m_playbackRatePausedState = PlaybackRatePausedState::RatePaused; + updateStates(); + } + return; + } else if (m_playbackRatePausedState == PlaybackRatePausedState::RatePaused) { + m_playbackRatePausedState = PlaybackRatePausedState::ShouldMoveToPlaying; + updateStates(); + } + + GstState state, pending; + gst_element_get_state(m_pipeline.get(), &state, &pending, 0); + if ((state != GST_STATE_PLAYING && state != GST_STATE_PAUSED) + || (pending == GST_STATE_PAUSED)) + return; + + updatePlaybackRate(); +} + +double MediaPlayerPrivateGStreamer::rate() const +{ + return m_playbackRate; +} + +void MediaPlayerPrivateGStreamer::setPreservesPitch(bool preservesPitch) +{ + GST_DEBUG_OBJECT(pipeline(), "Preserving audio pitch: %s", boolForPrinting(preservesPitch)); + m_shouldPreservePitch = preservesPitch; +} + +void MediaPlayerPrivateGStreamer::setPreload(MediaPlayer::Preload preload) +{ + if (isMediaStreamPlayer()) + return; + + GST_DEBUG_OBJECT(pipeline(), "Setting preload to %s", convertEnumerationToString(preload).utf8().data()); + if (preload == MediaPlayer::Preload::Auto && m_isLiveStream.value_or(false)) + return; + + m_preload = preload; + updateDownloadBufferingFlag(); + + if (m_isDelayingLoad && m_preload != MediaPlayer::Preload::None) { + m_isDelayingLoad = false; + commitLoad(); + } +} + +const PlatformTimeRanges& MediaPlayerPrivateGStreamer::buffered() const +{ + if (m_didErrorOccur || m_isLiveStream.value_or(false)) + return PlatformTimeRanges::emptyRanges(); + + MediaTime mediaDuration = durationMediaTime(); + if (!mediaDuration || mediaDuration.isPositiveInfinite()) + return PlatformTimeRanges::emptyRanges(); + + GRefPtr query = adoptGRef(gst_query_new_buffering(GST_FORMAT_PERCENT)); + + if (!gst_element_query(m_pipeline.get(), query.get())) + return PlatformTimeRanges::emptyRanges(); + + m_buffered.clear(); + unsigned numBufferingRanges = gst_query_get_n_buffering_ranges(query.get()); + for (unsigned index = 0; index < numBufferingRanges; index++) { + gint64 rangeStart = 0, rangeStop = 0; + if (gst_query_parse_nth_buffering_range(query.get(), index, &rangeStart, &rangeStop)) { + uint64_t startTime = gst_util_uint64_scale_int_round(toGstUnsigned64Time(mediaDuration), rangeStart, GST_FORMAT_PERCENT_MAX); + uint64_t stopTime = gst_util_uint64_scale_int_round(toGstUnsigned64Time(mediaDuration), rangeStop, GST_FORMAT_PERCENT_MAX); + m_buffered.add(MediaTime(startTime, GST_SECOND), MediaTime(stopTime, GST_SECOND)); + } + } + + // Fallback to the more general maxTimeLoaded() if no range has been found. + if (!m_buffered.length()) { + MediaTime loaded = maxTimeLoaded(); + if (loaded.isValid() && loaded) + m_buffered.add(MediaTime::zeroTime(), loaded); + } + + return m_buffered; +} + +MediaTime MediaPlayerPrivateGStreamer::maxMediaTimeSeekable() const +{ + GST_TRACE_OBJECT(pipeline(), "errorOccured: %s, isLiveStream: %s", boolForPrinting(m_didErrorOccur), boolForPrinting(m_isLiveStream)); + if (m_didErrorOccur) + return MediaTime::zeroTime(); + + if (m_isLiveStream.value_or(false)) + return MediaTime::zeroTime(); + + if (isMediaStreamPlayer()) + return MediaTime::zeroTime(); + + MediaTime duration = durationMediaTime(); + GST_DEBUG_OBJECT(pipeline(), "maxMediaTimeSeekable, duration: %s", toString(duration).utf8().data()); + // Infinite duration means live stream. + if (duration.isPositiveInfinite()) + return MediaTime::zeroTime(); + + return duration; +} + +MediaTime MediaPlayerPrivateGStreamer::maxTimeLoaded() const +{ + if (m_didErrorOccur) + return MediaTime::zeroTime(); + + MediaTime loaded = m_maxTimeLoaded; + if (m_isEndReached) + loaded = durationMediaTime(); + GST_LOG_OBJECT(pipeline(), "maxTimeLoaded: %s", toString(loaded).utf8().data()); + return loaded; +} + +bool MediaPlayerPrivateGStreamer::didLoadingProgress() const +{ + if (m_didErrorOccur || m_loadingStalled) + return false; + + if (WEBKIT_IS_WEB_SRC(m_source.get())) { + GST_LOG_OBJECT(pipeline(), "Last network read position: %" G_GUINT64_FORMAT ", current: %" G_GUINT64_FORMAT, m_readPositionAtLastDidLoadingProgress, m_networkReadPosition); + bool didLoadingProgress = m_readPositionAtLastDidLoadingProgress < m_networkReadPosition; + m_readPositionAtLastDidLoadingProgress = m_networkReadPosition; + GST_LOG_OBJECT(pipeline(), "didLoadingProgress: %s", boolForPrinting(didLoadingProgress)); + return didLoadingProgress; + } + + if (UNLIKELY(!m_pipeline || !durationMediaTime() || (!isMediaSource() && !totalBytes()))) + return false; + + MediaTime currentMaxTimeLoaded = maxTimeLoaded(); + bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress; + m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded; + GST_LOG_OBJECT(pipeline(), "didLoadingProgress: %s", boolForPrinting(didLoadingProgress)); + return didLoadingProgress; +} + +unsigned long long MediaPlayerPrivateGStreamer::totalBytes() const +{ + if (m_didErrorOccur || !m_source || m_isLiveStream.value_or(false) || isMediaStreamPlayer()) + return 0; + + if (m_totalBytes) + return m_totalBytes; + + GstFormat fmt = GST_FORMAT_BYTES; + gint64 length = 0; + if (gst_element_query_duration(m_source.get(), fmt, &length)) { + GST_INFO_OBJECT(pipeline(), "totalBytes %" G_GINT64_FORMAT, length); + m_totalBytes = static_cast(length); + m_isLiveStream = !length; + return m_totalBytes; + } + + // Fall back to querying the source pads manually. See also https://bugzilla.gnome.org/show_bug.cgi?id=638749 + GstIterator* iter = gst_element_iterate_src_pads(m_source.get()); + bool done = false; + while (!done) { + GValue item = G_VALUE_INIT; + switch (gst_iterator_next(iter, &item)) { + case GST_ITERATOR_OK: { + GstPad* pad = static_cast(g_value_get_object(&item)); + gint64 padLength = 0; + if (gst_pad_query_duration(pad, fmt, &padLength) && padLength > length) + length = padLength; + break; + } + case GST_ITERATOR_RESYNC: + gst_iterator_resync(iter); + break; + case GST_ITERATOR_ERROR: + FALLTHROUGH; + case GST_ITERATOR_DONE: + done = true; + break; + } + + g_value_unset(&item); + } + + gst_iterator_free(iter); + + GST_INFO_OBJECT(pipeline(), "totalBytes %" G_GINT64_FORMAT, length); + m_totalBytes = static_cast(length); + m_isLiveStream = !length; + return m_totalBytes; +} + +std::optional MediaPlayerPrivateGStreamer::isCrossOrigin(const SecurityOrigin& origin) const +{ + if (WEBKIT_IS_WEB_SRC(m_source.get())) + return webKitSrcIsCrossOrigin(WEBKIT_WEB_SRC(m_source.get()), origin); + return false; +} + +void MediaPlayerPrivateGStreamer::simulateAudioInterruption() +{ + GstMessage* message = gst_message_new_request_state(GST_OBJECT(m_pipeline.get()), GST_STATE_PAUSED); + gst_element_post_message(m_pipeline.get(), message); +} + +#if ENABLE(WEB_AUDIO) +void MediaPlayerPrivateGStreamer::ensureAudioSourceProvider() +{ + if (!m_audioSourceProvider) + m_audioSourceProvider = AudioSourceProviderGStreamer::create(); +} + +AudioSourceProvider* MediaPlayerPrivateGStreamer::audioSourceProvider() +{ + ensureAudioSourceProvider(); + return m_audioSourceProvider.get(); +} +#endif + +void MediaPlayerPrivateGStreamer::durationChanged() +{ + MediaTime previousDuration = durationMediaTime(); + m_cachedDuration = MediaTime::invalidTime(); + + // Avoid emitting durationChanged in the case where the previous + // duration was 0 because that case is already handled by the + // HTMLMediaElement. + if (previousDuration && durationMediaTime() != previousDuration) { + if (RefPtr player = m_player.get()) + player->durationChanged(); + } +} + +void MediaPlayerPrivateGStreamer::sourceSetup(GstElement* sourceElement) +{ + GST_DEBUG_OBJECT(pipeline(), "Source element set-up for %s", GST_ELEMENT_NAME(sourceElement)); + + m_source = sourceElement; + + if (WEBKIT_IS_WEB_SRC(m_source.get())) { + auto* source = WEBKIT_WEB_SRC_CAST(m_source.get()); + webKitWebSrcSetReferrer(source, m_referrer); + webKitWebSrcSetResourceLoader(source, m_loader); +#if ENABLE(MEDIA_STREAM) + } else if (WEBKIT_IS_MEDIA_STREAM_SRC(sourceElement)) { + RefPtr player = m_player.get(); + auto stream = m_streamPrivate.get(); + ASSERT(stream); + webkitMediaStreamSrcSetStream(WEBKIT_MEDIA_STREAM_SRC(sourceElement), stream, player && player->isVideoPlayer()); +#endif + } +} + +void MediaPlayerPrivateGStreamer::sourceSetupCallback(MediaPlayerPrivateGStreamer* player, GstElement* sourceElement) +{ + player->sourceSetup(sourceElement); +} + +bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState) +{ + ASSERT(m_pipeline); + + if (!m_isVisibleInViewport && newState > GST_STATE_PAUSED) { + GST_DEBUG_OBJECT(pipeline(), "Saving state for when player becomes visible: %s", gst_element_state_get_name(newState)); + m_invisiblePlayerState = newState; + return true; + } + + GstState currentState, pending; + gst_element_get_state(m_pipeline.get(), ¤tState, &pending, 0); + GST_DEBUG_OBJECT(pipeline(), "Changing state change to %s from %s with %s pending", gst_element_state_get_name(newState), + gst_element_state_get_name(currentState), gst_element_state_get_name(pending)); + + GstStateChangeReturn setStateResult = gst_element_set_state(m_pipeline.get(), newState); + GstState pausedOrPlaying = newState == GST_STATE_PLAYING ? GST_STATE_PAUSED : GST_STATE_PLAYING; + if (currentState != pausedOrPlaying && setStateResult == GST_STATE_CHANGE_FAILURE) + return false; + + // Create a timer when entering the READY state so that we can free resources if we stay for too long on READY. + // Also lets remove the timer if we request a state change for any state other than READY. See also https://bugs.webkit.org/show_bug.cgi?id=117354 + if (RefPtr player = m_player.get(); newState == GST_STATE_PAUSED && m_isEndReached && player && !player->isLooping() + && !isMediaSource() && !m_pausedTimerHandler.isActive()) { + // Max interval in seconds to stay in the PAUSED state after video finished on manual state change requests. + static const Seconds readyStateTimerDelay { 5_min }; + m_pausedTimerHandler.startOneShot(readyStateTimerDelay); + } else if (newState != GST_STATE_PAUSED) + m_pausedTimerHandler.stop(); + + return true; +} + +void MediaPlayerPrivateGStreamer::setPlaybinURL(const URL& url) +{ + // Clean out everything after file:// url path. + String cleanURLString(url.string()); + if (url.protocolIsFile()) + cleanURLString = cleanURLString.left(url.pathEnd()); + + m_url = URL { cleanURLString }; + GST_INFO_OBJECT(pipeline(), "Load %s", m_url.string().utf8().data()); + g_object_set(m_pipeline.get(), "uri", m_url.string().utf8().data(), nullptr); +} + +static void setSyncOnClock(GstElement *element, bool sync) +{ + if (!element) + return; + + if (!GST_IS_BIN(element)) { + g_object_set(element, "sync", sync, nullptr); + return; + } + + GUniquePtr iterator(gst_bin_iterate_sinks(GST_BIN_CAST(element))); + while (gst_iterator_foreach(iterator.get(), static_cast([](const GValue* item, void* syncPtr) { + bool* sync = static_cast(syncPtr); + setSyncOnClock(GST_ELEMENT_CAST(g_value_get_object(item)), *sync); + }), &sync) == GST_ITERATOR_RESYNC) + gst_iterator_resync(iterator.get()); +} + +void MediaPlayerPrivateGStreamer::syncOnClock(bool sync) +{ +#if !USE(WESTEROS_SINK) + setSyncOnClock(videoSink(), sync); + setSyncOnClock(audioSink(), sync); +#endif +} + +template +void MediaPlayerPrivateGStreamer::notifyPlayerOfTrack() +{ + if (UNLIKELY(!m_pipeline || !m_source)) + return; + + RefPtr player = m_player.get(); + if (!player) + return; + + ASSERT(m_isLegacyPlaybin); + + using TrackType = TrackPrivateBaseGStreamer::TrackType; + std::variant>*, HashMap>*, HashMap>*> variantTracks = static_cast>*>(0); + auto type(static_cast(variantTracks.index())); + const char* typeName; + bool* hasType; + switch (type) { + case TrackType::Audio: + typeName = "audio"; + hasType = &m_hasAudio; + variantTracks = &m_audioTracks; + break; + case TrackType::Video: + typeName = "video"; + hasType = &m_hasVideo; + variantTracks = &m_videoTracks; + break; + case TrackType::Text: + typeName = "text"; + hasType = nullptr; + variantTracks = &m_textTracks; + break; + default: + ASSERT_NOT_REACHED(); + } + auto& tracks = *std::get>*>(variantTracks); + + // Ignore notifications after a EOS. We don't want the tracks to disappear when the video is finished. + if (m_isEndReached && (type == TrackType::Audio || type == TrackType::Video)) + return; + + unsigned numberOfTracks = 0; + StringPrintStream numberOfTracksProperty; + numberOfTracksProperty.printf("n-%s", typeName); + g_object_get(m_pipeline.get(), numberOfTracksProperty.toCString().data(), &numberOfTracks, nullptr); + + GST_INFO_OBJECT(pipeline(), "Media has %d %s tracks", numberOfTracks, typeName); + + if (hasType) { + bool oldHasType = *hasType; + *hasType = numberOfTracks > 0; + if (oldHasType != *hasType) + player->characteristicChanged(); + + if (*hasType && type == TrackType::Video) + player->sizeChanged(); + } + + Vector validStreams; + StringPrintStream getPadProperty; + getPadProperty.printf("get-%s-pad", typeName); + + bool changed = false; + for (unsigned i = 0; i < numberOfTracks; ++i) { + GRefPtr pad; + g_signal_emit_by_name(m_pipeline.get(), getPadProperty.toCString().data(), i, &pad.outPtr(), nullptr); + ASSERT(pad); + if (!pad) + continue; + + AtomString streamId(TrackPrivateBaseGStreamer::trackIdFromPadStreamStartOrUniqueID(type, i, pad)); + + if (i < tracks.size()) { + RefPtr existingTrack = tracks.get(streamId); + if (existingTrack) { + ASSERT(existingTrack->index() == i); + // TODO: Position of index should remain the same on replay. + existingTrack->setIndex(i); + // If the video has been played twice, the track is still there, but we need + // to update the pad pointer. + if (existingTrack->pad() != pad) + existingTrack->setPad(GRefPtr(pad)); + continue; + } + } + + auto track = TrackPrivateType::create(*this, i, GRefPtr(pad)); + ASSERT(track->stringId() == streamId); + validStreams.append(track->stringId()); + if (!track->trackIndex() && (type == TrackType::Audio || type == TrackType::Video)) + track->setActive(true); + + std::variant variantTrack(&track.get()); + switch (variantTrack.index()) { + case TrackType::Audio: + player->addAudioTrack(*std::get(variantTrack)); + break; + case TrackType::Video: + player->addVideoTrack(*std::get(variantTrack)); + break; + case TrackType::Text: + player->addTextTrack(*std::get(variantTrack)); + break; + } + tracks.add(track->stringId(), WTFMove(track)); + changed = true; + } + + // Purge invalid tracks + changed = changed || tracks.removeIf([validStreams](auto& keyAndValue) { + return !validStreams.contains(keyAndValue.key); + }); + + if (changed) + player->mediaEngineUpdated(); +} + +bool MediaPlayerPrivateGStreamer::hasFirstVideoSampleReachedSink() const +{ + Locker sampleLocker { m_sampleMutex }; + return !!m_sample; +} + +void MediaPlayerPrivateGStreamer::videoSinkCapsChanged(GstPad* videoSinkPad) +{ + GRefPtr caps = adoptGRef(gst_pad_get_current_caps(videoSinkPad)); + if (!caps) { + // This can happen when downgrading the state of the pipeline, which causes the caps to be unset. + return; + } + // We're in videoSinkPad streaming thread. + ASSERT(!isMainThread()); + GST_DEBUG_OBJECT(videoSinkPad, "Received new caps: %" GST_PTR_FORMAT, caps.get()); + + if (!hasFirstVideoSampleReachedSink()) { + // We want to wait for the sink to receive the first buffer before emitting dimensions, since only by then we + // are guaranteed that any potential tag event with a rotation has been handled. + GST_DEBUG_OBJECT(videoSinkPad, "Ignoring notify::caps until the first buffer reaches the sink."); + return; + } + + RunLoop::main().dispatch([weakThis = WeakPtr { *this }, this, caps = WTFMove(caps)] { + if (!weakThis) + return; + updateVideoSizeAndOrientationFromCaps(caps.get()); + }); +} + +void MediaPlayerPrivateGStreamer::handleTextSample(GstSample* sample, const char* streamId) +{ + for (auto& track : m_textTracks.values()) { + if (!strcmp(track->stringId().string().utf8().data(), streamId)) { + track->handleSample(sample); + return; + } + } + + GST_WARNING_OBJECT(m_pipeline.get(), "Got sample with unknown stream ID %s.", streamId); +} + +MediaTime MediaPlayerPrivateGStreamer::platformDuration() const +{ + if (!m_pipeline) + return MediaTime::invalidTime(); + + if (isMediaStreamPlayer()) + return MediaTime::positiveInfiniteTime(); + + GST_TRACE_OBJECT(pipeline(), "errorOccured: %s, pipeline state: %s", boolForPrinting(m_didErrorOccur), gst_element_state_get_name(GST_STATE(m_pipeline.get()))); + if (m_didErrorOccur) + return MediaTime::invalidTime(); + + // The duration query would fail on a not-prerolled pipeline. + if (GST_STATE(m_pipeline.get()) < GST_STATE_PAUSED) + return MediaTime::invalidTime(); + + int64_t duration = 0; + if (!gst_element_query_duration(m_pipeline.get(), GST_FORMAT_TIME, &duration) || !GST_CLOCK_TIME_IS_VALID(duration)) { + GST_DEBUG_OBJECT(pipeline(), "Time duration query failed for %s", m_url.string().utf8().data()); + // https://www.w3.org/TR/2011/WD-html5-20110113/video.html#getting-media-metadata + // In order to be strict with the spec, consider that not "enough of the media data has been fetched to determine + // the duration of the media resource" and therefore return invalidTime only when we know for sure that the + // stream isn't live (treating empty value as unsure). + return m_isLiveStream.value_or(true) ? MediaTime::positiveInfiniteTime() : MediaTime::invalidTime(); + } + + GST_LOG_OBJECT(pipeline(), "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS(duration)); + return MediaTime(duration, GST_SECOND); +} + +bool MediaPlayerPrivateGStreamer::isMuted() const +{ + GST_INFO_OBJECT(pipeline(), "Player is muted: %s", boolForPrinting(m_isMuted)); + return m_isMuted; +} + +void MediaPlayerPrivateGStreamer::commitLoad() +{ + ASSERT(!m_isDelayingLoad); + GST_DEBUG_OBJECT(pipeline(), "Committing load."); + + // GStreamer needs to have the pipeline set to a paused state to + // start providing anything useful. + changePipelineState(GST_STATE_PAUSED); + + updateDownloadBufferingFlag(); + updateStates(); +} + +void MediaPlayerPrivateGStreamer::fillTimerFired() +{ + if (m_didErrorOccur) { + GST_DEBUG_OBJECT(pipeline(), "[Buffering] An error occurred, disabling the fill timer"); + m_fillTimer.stop(); + return; + } + + GRefPtr query = adoptGRef(gst_query_new_buffering(GST_FORMAT_PERCENT)); + double fillStatus = 100.0; + GstBufferingMode mode = GST_BUFFERING_DOWNLOAD; + + if (gst_element_query(pipeline(), query.get())) { + gst_query_parse_buffering_stats(query.get(), &mode, nullptr, nullptr, nullptr); + + int percentage; + gst_query_parse_buffering_percent(query.get(), nullptr, &percentage); + fillStatus = percentage; + } else if (m_httpResponseTotalSize) { + GST_DEBUG_OBJECT(pipeline(), "[Buffering] Query failed, falling back to network read position estimation"); + fillStatus = 100.0 * (static_cast(m_networkReadPosition) / static_cast(m_httpResponseTotalSize)); + } else { + GST_DEBUG_OBJECT(pipeline(), "[Buffering] Unable to determine on-disk buffering status"); + return; + } + + updateBufferingStatus(mode, fillStatus); +} + +void MediaPlayerPrivateGStreamer::loadStateChanged() +{ + updateStates(); +} + +void MediaPlayerPrivateGStreamer::timeChanged(const MediaTime& seekedTime) +{ + updateStates(); + GST_DEBUG_OBJECT(pipeline(), "Emitting timeChanged notification (seekCompleted:%d)", seekedTime.isValid()); + if (RefPtr player = m_player.get()) { + if (seekedTime.isValid()) + player->seeked(seekedTime); + player->timeChanged(); + } +} + +void MediaPlayerPrivateGStreamer::loadingFailed(MediaPlayer::NetworkState networkError, MediaPlayer::ReadyState readyState, bool forceNotifications) +{ + GST_WARNING("Loading failed, error: %s", convertEnumerationToString(networkError).utf8().data()); + + RefPtr player = m_player.get(); + + m_didErrorOccur = true; + if (forceNotifications || m_networkState != networkError) { + m_networkState = networkError; + if (player) + player->networkStateChanged(); + } + if (forceNotifications || m_readyState != readyState) { + m_readyState = readyState; + if (player) + player->readyStateChanged(); + } + + // Loading failed, remove ready timer. + m_pausedTimerHandler.stop(); +} + +GstElement* MediaPlayerPrivateGStreamer::createAudioSink() +{ +#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK) + // If audio is being controlled by an another pipeline, creating sink here may interfere with + // audio playback. Instead, check if an audio sink was setup in handleMessage and use it. + return nullptr; +#endif + + RefPtr player = m_player.get(); + if (!player) + return nullptr; + + // For platform specific audio sinks, they need to be properly upranked so that they get properly autoplugged. + + auto role = player->isVideoPlayer() ? "video"_s : "music"_s; + GstElement* audioSink = createPlatformAudioSink(role); + RELEASE_ASSERT(audioSink); + if (!audioSink) + return nullptr; + +#if ENABLE(WEB_AUDIO) + GstElement* audioSinkBin = gst_bin_new("audio-sink"); + ensureAudioSourceProvider(); + m_audioSourceProvider->configureAudioBin(audioSinkBin, audioSink); + return audioSinkBin; +#else + return audioSink; +#endif +} + +bool MediaPlayerPrivateGStreamer::isMediaStreamPlayer() const +{ +#if ENABLE(MEDIA_STREAM) + if (m_source) + return WEBKIT_IS_MEDIA_STREAM_SRC(m_source.get()); +#endif + return m_url.protocolIs("mediastream"_s); +} + +GstClockTime MediaPlayerPrivateGStreamer::gstreamerPositionFromSinks() const +{ + gint64 gstreamerPosition = GST_CLOCK_TIME_NONE; + // Asking directly to the sinks and choosing the highest value is faster than asking to the pipeline. + GST_TRACE_OBJECT(pipeline(), "Querying position to audio sink (if any)."); + GRefPtr query = adoptGRef(gst_query_new_position(GST_FORMAT_TIME)); + if (m_audioSink && gst_element_query(m_audioSink.get(), query.get())) { + gint64 audioPosition = GST_CLOCK_TIME_NONE; + gst_query_parse_position(query.get(), 0, &audioPosition); + if (GST_CLOCK_TIME_IS_VALID(audioPosition)) + gstreamerPosition = audioPosition; + GST_TRACE_OBJECT(pipeline(), "Audio position %" GST_TIME_FORMAT, GST_TIME_ARGS(audioPosition)); + query = adoptGRef(gst_query_new_position(GST_FORMAT_TIME)); + } + GST_TRACE_OBJECT(pipeline(), "Querying position to video sink (if any)."); + RefPtr player = m_player.get(); + if (player && player->isVideoPlayer() && m_videoSink && gst_element_query(m_videoSink.get(), query.get())) { + gint64 videoPosition = GST_CLOCK_TIME_NONE; + gst_query_parse_position(query.get(), 0, &videoPosition); + GST_TRACE_OBJECT(pipeline(), "Video position %" GST_TIME_FORMAT, GST_TIME_ARGS(videoPosition)); + if (GST_CLOCK_TIME_IS_VALID(videoPosition) && (!GST_CLOCK_TIME_IS_VALID(gstreamerPosition) + || (m_playbackRate >= 0 && videoPosition > gstreamerPosition) + || (m_playbackRate < 0 && videoPosition < gstreamerPosition))) + gstreamerPosition = videoPosition; + } + return static_cast(gstreamerPosition); +} + +MediaTime MediaPlayerPrivateGStreamer::playbackPosition() const +{ + GST_TRACE_OBJECT(pipeline(), "isEndReached: %s, seeking: %s, seekTime: %s", boolForPrinting(m_isEndReached), boolForPrinting(m_isSeeking), m_seekTarget.time.toString().utf8().data()); + +#if ENABLE(MEDIA_STREAM) + RefPtr player = m_player.get(); + if (m_streamPrivate && player && player->isVideoPlayer() && !hasFirstVideoSampleReachedSink()) + return MediaTime::zeroTime(); +#endif + + if (m_isSeeking) + return m_seekTarget.time; + if (m_isEndReached) + return m_playbackRate > 0 ? durationMediaTime() : MediaTime::zeroTime(); + + if (m_isCachedPositionValid) { + GST_TRACE_OBJECT(pipeline(), "Returning cached position: %s", m_cachedPosition.toString().utf8().data()); + return m_cachedPosition; + } + + GstClockTime gstreamerPosition = gstreamerPositionFromSinks(); + GST_TRACE_OBJECT(pipeline(), "Position %" GST_TIME_FORMAT ", canFallBackToLastFinishedSeekPosition: %s", GST_TIME_ARGS(gstreamerPosition), boolForPrinting(m_canFallBackToLastFinishedSeekPosition)); + + // Cached position is marked as non valid here but we might fail to get a new one so initializing to this as "educated guess". + MediaTime playbackPosition = m_cachedPosition; + + if (GST_CLOCK_TIME_IS_VALID(gstreamerPosition)) + playbackPosition = MediaTime(gstreamerPosition, GST_SECOND); + else if (m_canFallBackToLastFinishedSeekPosition) + playbackPosition = m_seekTarget.time; + + setCachedPosition(playbackPosition); + invalidateCachedPositionOnNextIteration(); + return playbackPosition; +} + +void MediaPlayerPrivateGStreamer::updateEnabledVideoTrack() +{ + VideoTrackPrivateGStreamer* wantedTrack = nullptr; + for (auto& pair : m_videoTracks) { + auto& track = pair.value.get(); + if (track.selected()) { + wantedTrack = &track; + break; + } + } + + // No active track, no changes. + if (!wantedTrack) + return; + + if (m_isLegacyPlaybin) { + GST_DEBUG_OBJECT(m_pipeline.get(), "Setting playbin2 current-video=%d", wantedTrack->trackIndex()); + g_object_set(m_pipeline.get(), "current-video", wantedTrack->trackIndex(), nullptr); + } else { + m_wantedVideoStreamId = wantedTrack->stringId(); + playbin3SendSelectStreamsIfAppropriate(); + } +} + +void MediaPlayerPrivateGStreamer::updateEnabledAudioTrack() +{ + AudioTrackPrivateGStreamer* wantedTrack = nullptr; + for (auto& pair : m_audioTracks) { + auto& track = pair.value.get(); + if (track.enabled()) { + wantedTrack = &track; + break; + } + } + + // No active track, no changes. + if (!wantedTrack) + return; + + if (m_isLegacyPlaybin) { + GST_DEBUG_OBJECT(m_pipeline.get(), "Setting playbin2 current-audio=%d", wantedTrack->trackIndex()); + g_object_set(m_pipeline.get(), "current-audio", wantedTrack->trackIndex(), nullptr); + } else { + m_wantedAudioStreamId = wantedTrack->stringId(); + playbin3SendSelectStreamsIfAppropriate(); + } +} + +void MediaPlayerPrivateGStreamer::playbin3SendSelectStreamsIfAppropriate() +{ + ASSERT(!m_isLegacyPlaybin); + + bool haveDifferentStreamIds = (m_wantedAudioStreamId != m_currentAudioStreamId || m_wantedVideoStreamId != m_currentVideoStreamId); + bool shouldSendSelectStreams = !m_waitingForStreamsSelectedEvent && haveDifferentStreamIds && m_currentState == GST_STATE_PLAYING; + GST_DEBUG_OBJECT(m_pipeline.get(), "Checking if to send SELECT_STREAMS, m_waitingForStreamsSelectedEvent = %s, haveDifferentStreamIds = %s, m_currentState = %s... shouldSendSelectStreams = %s", + boolForPrinting(m_waitingForStreamsSelectedEvent), boolForPrinting(haveDifferentStreamIds), gst_element_state_get_name(m_currentState), boolForPrinting(shouldSendSelectStreams)); + if (!shouldSendSelectStreams) + return; + + GList* streams = nullptr; + if (!m_wantedVideoStreamId.isNull()) { + m_requestedVideoStreamId = m_wantedVideoStreamId; + streams = g_list_append(streams, g_strdup(m_wantedVideoStreamId.string().utf8().data())); + } + if (!m_wantedAudioStreamId.isNull()) { + m_requestedAudioStreamId = m_wantedAudioStreamId; + streams = g_list_append(streams, g_strdup(m_wantedAudioStreamId.string().utf8().data())); + } + if (!m_wantedTextStreamId.isNull()) { + m_requestedTextStreamId = m_wantedTextStreamId; + streams = g_list_append(streams, g_strdup(m_wantedTextStreamId.string().utf8().data())); + } + + if (!streams) + return; + + m_waitingForStreamsSelectedEvent = true; + gst_element_send_event(m_pipeline.get(), gst_event_new_select_streams(streams)); + g_list_free_full(streams, reinterpret_cast(g_free)); +} + +void MediaPlayerPrivateGStreamer::updateTracks(const GRefPtr& collectionOwner) +{ + ASSERT(!m_isLegacyPlaybin); + + bool oldHasAudio = m_hasAudio; + bool oldHasVideo = m_hasVideo; + + RefPtr player = m_player.get(); + + // fast/mediastream/MediaStream-video-element-remove-track.html expects audio tracks gone, not deactivated. + if (player) { + for (auto& track : m_audioTracks.values()) + player->removeAudioTrack(track); + } + m_audioTracks.clear(); + + for (auto& track : m_videoTracks.values()) + track->setActive(false); + for (auto& track : m_textTracks.values()) + track->setActive(false); + + auto scopeExit = makeScopeExit([oldHasAudio, oldHasVideo, protectedThis = WeakPtr { *this }, this] { + if (!protectedThis) + return; + + RefPtr player = m_player.get(); + + m_hasAudio = !m_audioTracks.isEmpty(); + m_hasVideo = false; + + for (auto& track : m_videoTracks.values()) { + if (track->selected()) { + m_hasVideo = true; + break; + } + } + + if (player) { + if (oldHasVideo != m_hasVideo || oldHasAudio != m_hasAudio) + player->characteristicChanged(); + + if (!oldHasVideo && m_hasVideo) + player->sizeChanged(); + + player->mediaEngineUpdated(); + } + + if (!m_hasAudio && !m_hasVideo) + didEnd(); + }); + + if (!m_streamCollection) + return; + + using TextTrackPrivateGStreamer = InbandTextTrackPrivateGStreamer; +#define CREATE_OR_SELECT_TRACK(type, Type) G_STMT_START { \ + bool isTrackCached = m_##type##Tracks.contains(streamId); \ + if (!isTrackCached) { \ + auto track = Type##TrackPrivateGStreamer::create(*this, type##TrackIndex, stream); \ + if (player) \ + player->add##Type##Track(track); \ + m_##type##Tracks.add(streamId, WTFMove(track)); \ + } \ + auto track = m_##type##Tracks.get(streamId); \ + if (isTrackCached) \ + track->updateConfigurationFromCaps(WTFMove(caps)); \ + auto trackId = track->stringId(); \ + if (!type##TrackIndex) { \ + m_wanted##Type##StreamId = trackId; \ + m_requested##Type##StreamId = trackId; \ + track->setActive(true); \ + } \ + type##TrackIndex++; \ + } G_STMT_END + + bool useMediaSource = isMediaSource(); + unsigned audioTrackIndex = 0; + unsigned videoTrackIndex = 0; + unsigned textTrackIndex = 0; + unsigned length = gst_stream_collection_get_size(m_streamCollection.get()); + GST_DEBUG_OBJECT(pipeline(), "Received STREAM_COLLECTION message with upstream id \"%s\" from %" GST_PTR_FORMAT " defining the following streams:", gst_stream_collection_get_upstream_id(m_streamCollection.get()), collectionOwner.get()); + for (unsigned i = 0; i < length; i++) { + auto* stream = gst_stream_collection_get_stream(m_streamCollection.get(), i); + RELEASE_ASSERT(stream); + auto streamId = AtomString::fromLatin1(gst_stream_get_stream_id(stream)); + auto type = gst_stream_get_stream_type(stream); + auto caps = adoptGRef(gst_stream_get_caps(stream)); + + GST_DEBUG_OBJECT(pipeline(), "#%u %s track with ID %s and caps %" GST_PTR_FORMAT, i, gst_stream_type_get_name(type), streamId.string().ascii().data(), caps.get()); + + if (type & GST_STREAM_TYPE_AUDIO) { + CREATE_OR_SELECT_TRACK(audio, Audio); + configureMediaStreamAudioTracks(); + } else if (type & GST_STREAM_TYPE_VIDEO && player && player->isVideoPlayer()) + CREATE_OR_SELECT_TRACK(video, Video); + else if (type & GST_STREAM_TYPE_TEXT && !useMediaSource) + CREATE_OR_SELECT_TRACK(text, Text); + else + GST_WARNING("Unknown track type found for stream %s", streamId.string().ascii().data()); + } +#undef CREATE_OR_SELECT_TRACK +} + +void MediaPlayerPrivateGStreamer::handleStreamCollectionMessage(GstMessage* message) +{ + if (m_isLegacyPlaybin) + return; + + if (!m_source) + return; + + // GStreamer workaround: Unfortunately, when we have a stream-collection aware source (like + // WebKitMediaSrc) parsebin and decodebin3 emit their own stream-collection messages, but late, + // and sometimes with duplicated streams. Let's only listen for stream-collection messages from + // the source to avoid these issues. + if (!(g_str_has_prefix(GST_OBJECT_NAME(m_source.get()), "filesrc") || WEBKIT_IS_WEB_SRC(m_source.get())) && GST_MESSAGE_SRC(message) != GST_OBJECT(m_source.get())) { + GST_DEBUG_OBJECT(pipeline(), "Ignoring redundant STREAM_COLLECTION from %" GST_PTR_FORMAT, message->src); + return; + } + + ASSERT(GST_MESSAGE_TYPE(message) == GST_MESSAGE_STREAM_COLLECTION); + gst_message_parse_stream_collection(message, &m_streamCollection.outPtr()); + + auto callback = [player = WeakPtr { *this }, owner = GRefPtr(GST_MESSAGE_SRC(message))] { + if (player) + player->updateTracks(owner); + }; + + GST_DEBUG_OBJECT(pipeline(), "Updating tracks"); + callOnMainThreadAndWait(WTFMove(callback)); + GST_DEBUG_OBJECT(pipeline(), "Updating tracks DONE"); +} + +bool MediaPlayerPrivateGStreamer::handleNeedContextMessage(GstMessage* message) +{ + ASSERT(GST_MESSAGE_TYPE(message) == GST_MESSAGE_NEED_CONTEXT); + + const gchar* contextType; + if (!gst_message_parse_context_type(message, &contextType)) + return false; + + GST_DEBUG_OBJECT(pipeline(), "Handling %s need-context message for %s", contextType, GST_MESSAGE_SRC_NAME(message)); + + if (!g_strcmp0(contextType, WEBKIT_WEB_SRC_RESOURCE_LOADER_CONTEXT_TYPE_NAME)) { + auto context = adoptGRef(gst_context_new(WEBKIT_WEB_SRC_RESOURCE_LOADER_CONTEXT_TYPE_NAME, FALSE)); + GstStructure* contextStructure = gst_context_writable_structure(context.get()); + + gst_structure_set(contextStructure, "loader", G_TYPE_POINTER, m_loader.get(), nullptr); + gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context.get()); + return true; + } + +#if ENABLE(ENCRYPTED_MEDIA) + if (!g_strcmp0(contextType, "drm-preferred-decryption-system-id")) { + initializationDataEncountered(parseInitDataFromProtectionMessage(message)); + bool isCDMAttached = waitForCDMAttachment(); + if (isCDMAttached && !isPlayerShuttingDown() && !m_cdmInstance->keySystem().isEmpty()) { + const char* preferredKeySystemUuid = GStreamerEMEUtilities::keySystemToUuid(m_cdmInstance->keySystem()); + GST_INFO_OBJECT(pipeline(), "working with key system %s, continuing with key system %s on %s", m_cdmInstance->keySystem().utf8().data(), preferredKeySystemUuid, GST_MESSAGE_SRC_NAME(message)); + + GRefPtr context = adoptGRef(gst_context_new("drm-preferred-decryption-system-id", FALSE)); + GstStructure* contextStructure = gst_context_writable_structure(context.get()); + gst_structure_set(contextStructure, "decryption-system-id", G_TYPE_STRING, preferredKeySystemUuid, nullptr); + gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context.get()); + return true; + } + + GST_WARNING_OBJECT(pipeline(), "waiting for a CDM failed, no CDM available"); + return false; + } +#endif // ENABLE(ENCRYPTED_MEDIA) + + GST_DEBUG_OBJECT(pipeline(), "Unhandled %s need-context message for %s", contextType, GST_MESSAGE_SRC_NAME(message)); + return false; +} + +// Returns the size of the video. +FloatSize MediaPlayerPrivateGStreamer::naturalSize() const +{ + if (!hasVideo()) + return FloatSize(); + + if (!m_videoSize.isEmpty()) + return m_videoSize; + +#if USE(GSTREAMER_HOLEPUNCH) + // When using the holepuch we may not be able to get the video frames size, so we can't use + // it. But we need to report some non empty naturalSize for the player's GraphicsLayer + // to be properly created. + return s_holePunchDefaultFrameSize; +#endif + + return m_videoSize; +} + +void MediaPlayerPrivateGStreamer::configureMediaStreamAudioTracks() +{ +#if ENABLE(MEDIA_STREAM) + if (WEBKIT_IS_MEDIA_STREAM_SRC(m_source.get())) + webkitMediaStreamSrcConfigureAudioTracks(WEBKIT_MEDIA_STREAM_SRC(m_source.get()), volume(), isMuted(), !m_isPaused); +#endif +} + +void MediaPlayerPrivateGStreamer::setVolume(float volume) +{ + if (!m_volumeElement) + return; + + GST_DEBUG_OBJECT(pipeline(), "Setting volume: %f", volume); + gst_stream_volume_set_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_LINEAR, static_cast(volume)); + configureMediaStreamAudioTracks(); +} + +float MediaPlayerPrivateGStreamer::volume() const +{ + if (!m_volumeElement) + return 0; + + auto volume = gst_stream_volume_get_volume(m_volumeElement.get(), GST_STREAM_VOLUME_FORMAT_LINEAR); + GST_DEBUG_OBJECT(pipeline(), "Volume: %f", volume); + return volume; +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfVolumeChange() +{ + RefPtr player = m_player.get(); + if (!player || !m_volumeElement) + return; + + // get_volume() can return values superior to 1.0 if the user applies software user gain via + // third party application (GNOME volume control for instance). + auto oldVolume = this->volume(); + auto volume = CLAMP(oldVolume, 0.0, 1.0); + + if (volume != oldVolume) + GST_DEBUG_OBJECT(pipeline(), "Volume value (%f) was not in [0,1] range. Clamped to %f", oldVolume, volume); + player->volumeChanged(volume); +} + +void MediaPlayerPrivateGStreamer::volumeChangedCallback(MediaPlayerPrivateGStreamer* player) +{ + if (player->isPlayerShuttingDown()) + return; + + // This is called when m_volumeElement receives the notify::volume signal. + GST_DEBUG_OBJECT(player->pipeline(), "Volume changed to: %f", player->volume()); + + player->m_notifier->notify(MainThreadNotification::VolumeChanged, [player] { + player->notifyPlayerOfVolumeChange(); + }); +} + +MediaPlayer::NetworkState MediaPlayerPrivateGStreamer::networkState() const +{ + return m_networkState; +} + +MediaPlayer::ReadyState MediaPlayerPrivateGStreamer::readyState() const +{ + return m_readyState; +} + +void MediaPlayerPrivateGStreamer::setMuted(bool shouldMute) +{ + GST_DEBUG_OBJECT(pipeline(), "Attempting to set muted state to %s", boolForPrinting(shouldMute)); + + if (!m_volumeElement || shouldMute == isMuted()) + return; + + GST_INFO_OBJECT(pipeline(), "Setting muted state to %s", boolForPrinting(shouldMute)); + g_object_set(m_volumeElement.get(), "mute", static_cast(shouldMute), nullptr); + configureMediaStreamAudioTracks(); +} + +void MediaPlayerPrivateGStreamer::notifyPlayerOfMute() +{ + RefPtr player = m_player.get(); + if (!player || !m_volumeElement) + return; + + gboolean value; + bool isMuted; + g_object_get(m_volumeElement.get(), "mute", &value, nullptr); + isMuted = value; + if (isMuted == m_isMuted) + return; + + m_isMuted = isMuted; + GST_DEBUG_OBJECT(pipeline(), "Notifying player of new mute value: %s", boolForPrinting(isMuted)); + player->muteChanged(m_isMuted); +} + +void MediaPlayerPrivateGStreamer::muteChangedCallback(MediaPlayerPrivateGStreamer* player) +{ + // This is called when m_volumeElement receives the notify::mute signal. + player->m_notifier->notify(MainThreadNotification::MuteChanged, [player] { + player->notifyPlayerOfMute(); + }); +} + +void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) +{ + GUniqueOutPtr err; + GUniqueOutPtr debug; + MediaPlayer::NetworkState error; + bool issueError = true; + bool attemptNextLocation = false; + const GstStructure* structure = gst_message_get_structure(message); + GstState requestedState, currentState; + + m_canFallBackToLastFinishedSeekPosition = false; + + if (structure) { + const gchar* messageTypeName = gst_structure_get_name(structure); + + // Redirect messages are sent from elements, like qtdemux, to + // notify of the new location(s) of the media. + if (!g_strcmp0(messageTypeName, "redirect")) { + mediaLocationChanged(message); + return; + } + } + + RefPtr player = m_player.get(); + + // We ignore state changes from internal elements. They are forwarded to playbin2 anyway. + bool messageSourceIsPlaybin = GST_MESSAGE_SRC(message) == reinterpret_cast(m_pipeline.get()); + + GST_LOG_OBJECT(pipeline(), "Message %s received from element %s", GST_MESSAGE_TYPE_NAME(message), GST_MESSAGE_SRC_NAME(message)); + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_ERROR: + gst_message_parse_error(message, &err.outPtr(), &debug.outPtr()); + GST_ERROR_OBJECT(pipeline(), "%s (url=%s) (code=%d)", err->message, m_url.string().utf8().data(), err->code); + + if (m_shouldResetPipeline || m_didErrorOccur) + break; + + m_errorMessage = String::fromLatin1(err->message); + + error = MediaPlayer::NetworkState::Empty; + if (g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_CODEC_NOT_FOUND) + || g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_DECRYPT) + || g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_DECRYPT_NOKEY) + || g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE) + || g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED) + || g_error_matches(err.get(), GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN) + || g_error_matches(err.get(), GST_CORE_ERROR, GST_CORE_ERROR_PAD) + || g_error_matches(err.get(), GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_NOT_FOUND)) + error = MediaPlayer::NetworkState::FormatError; + else if (g_error_matches(err.get(), GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) { + GST_ERROR_OBJECT(pipeline(), "Decode error, let the Media element emit a stalled event."); + m_loadingStalled = true; + error = MediaPlayer::NetworkState::DecodeError; + attemptNextLocation = true; + } else if (err->domain == GST_STREAM_ERROR) { + error = MediaPlayer::NetworkState::DecodeError; + attemptNextLocation = true; + } else if (err->domain == GST_RESOURCE_ERROR) + error = MediaPlayer::NetworkState::NetworkError; + + if (attemptNextLocation) + issueError = !loadNextLocation(); + if (issueError) { + m_didErrorOccur = true; + if (m_networkState != error) { + m_networkState = error; + if (player) + player->networkStateChanged(); + } + } + break; + case GST_MESSAGE_WARNING: + gst_message_parse_warning(message, &err.outPtr(), &debug.outPtr()); + GST_WARNING_OBJECT(pipeline(), "%s (url=%s) (code=%d)", err->message, m_url.string().utf8().data(), err->code); + break; + case GST_MESSAGE_EOS: { + // In some specific cases, an EOS GstEvent can happen right before a seek. The event is translated + // by playbin as an EOS GstMessage and posted to the bus, waiting to be forwarded to the main thread. + // The EOS message (now irrelevant after the seek) is received and processed right after the seek, + // causing the termination of the media at the player private and upper levels. This can even happen + // after the seek has completed (m_isSeeking already false). + // The code below detects that condition by ensuring that the playback is coherent with the EOS message, + // that is, if we're still playing somewhere inside the playable ranges, there should be no EOS at + // all. If that's the case, it's considered to be one of those spureous EOS and is ignored. + // Live streams (infinite duration) are special and we still have to detect legitimate EOS there, so + // this message bailout isn't done in those cases. + MediaTime playbackPosition = MediaTime::invalidTime(); + MediaTime duration = durationMediaTime(); + GstClockTime gstreamerPosition = gstreamerPositionFromSinks(); + bool eosFlagIsSetInSink = false; + if (player && player->isVideoPlayer()) { + GRefPtr sinkPad = adoptGRef(gst_element_get_static_pad(m_videoSink.get(), "sink")); + eosFlagIsSetInSink = sinkPad && GST_PAD_IS_EOS(sinkPad.get()); + } + + if (!eosFlagIsSetInSink && m_audioSink) { + GRefPtr sinkPad = adoptGRef(gst_element_get_static_pad(m_audioSink.get(), "sink")); + eosFlagIsSetInSink = sinkPad && GST_PAD_IS_EOS(sinkPad.get()); + } + + if (GST_CLOCK_TIME_IS_VALID(gstreamerPosition)) + playbackPosition = MediaTime(gstreamerPosition, GST_SECOND); + if (!player->isLooping() && !eosFlagIsSetInSink && playbackPosition.isValid() && duration.isValid() + && ((m_playbackRate >= 0 && playbackPosition < duration && duration.isFinite()) + || (m_playbackRate < 0 && playbackPosition > MediaTime::zeroTime()))) { + GST_DEBUG_OBJECT(pipeline(), "EOS received but position %s is still in the finite playable limits [%s, %s], ignoring it", + playbackPosition.toString().utf8().data(), MediaTime::zeroTime().toString().utf8().data(), duration.toString().utf8().data()); + break; + } + didEnd(); + break; + } + case GST_MESSAGE_ASYNC_DONE: + if (!messageSourceIsPlaybin || m_isDelayingLoad) + break; + + // The MediaPlayerPrivateGStreamer superclass now processes what it needs by calling updateStates() in handleMessage() for + // GST_MESSAGE_STATE_CHANGED. However, subclasses still need to override asyncStateChangeDone() to do their own stuff. + didPreroll(); + break; + case GST_MESSAGE_STATE_CHANGED: { + GstState newState; + gst_message_parse_state_changed(message, ¤tState, &newState, nullptr); + +#if USE(GSTREAMER_HOLEPUNCH) && (USE(WPEWEBKIT_PLATFORM_BCM_NEXUS) || USE(WESTEROS_SINK)) + if (currentState <= GST_STATE_READY && newState >= GST_STATE_READY) { + // If we didn't create a video sink, store a reference to the created one. + if (!m_videoSink) { + // Detect the videoSink element. Getting the video-sink property of the pipeline requires + // locking some elements, which may lead to deadlocks during playback. Instead, identify + // the videoSink based on its metadata. + GstElement* element = GST_ELEMENT(GST_MESSAGE_SRC(message)); + if (GST_OBJECT_FLAG_IS_SET(element, GST_ELEMENT_FLAG_SINK)) { + const gchar* klassStr = gst_element_get_metadata(element, "klass"); + if (strstr(klassStr, "Sink") && strstr(klassStr, "Video")) { + m_videoSink = element; + + // Ensure that there's a buffer with the transparent rectangle available when playback is going to start. + pushNextHolePunchBuffer(); + } + } + } + } +#endif + +#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK) + if (currentState <= GST_STATE_READY && newState >= GST_STATE_READY) { + // If we didn't create an audio sink, store a reference to the created one. + if (!m_audioSink) { + // Detect an audio sink element. + GstElement* element = GST_ELEMENT(GST_MESSAGE_SRC(message)); + if (GST_OBJECT_FLAG_IS_SET(element, GST_ELEMENT_FLAG_SINK)) { + const gchar* klassStr = gst_element_get_metadata(element, "klass"); + if (strstr(klassStr, "Sink") && strstr(klassStr, "Audio")) + m_audioSink = element; + } + } + } +#endif + + if (!messageSourceIsPlaybin || m_isDelayingLoad) + break; + + GST_DEBUG_OBJECT(pipeline(), "Changed state from %s to %s", gst_element_state_get_name(currentState), gst_element_state_get_name(newState)); + + if (!m_isLegacyPlaybin && currentState == GST_STATE_PAUSED && newState == GST_STATE_PLAYING) + playbin3SendSelectStreamsIfAppropriate(); + updateStates(); + checkPlayingConsistency(); + + break; + } + case GST_MESSAGE_BUFFERING: + processBufferingStats(message); + break; + case GST_MESSAGE_DURATION_CHANGED: + // Duration in MSE is managed by MediaSource, SourceBuffer and AppendPipeline. + if (messageSourceIsPlaybin && !isMediaSource()) + durationChanged(); + break; + case GST_MESSAGE_REQUEST_STATE: + gst_message_parse_request_state(message, &requestedState); + gst_element_get_state(m_pipeline.get(), ¤tState, nullptr, 250 * GST_NSECOND); + if (requestedState < currentState) { + GST_INFO_OBJECT(pipeline(), "Element %s requested state change to %s", GST_MESSAGE_SRC_NAME(message), + gst_element_state_get_name(requestedState)); + m_requestedState = requestedState; + if (!changePipelineState(requestedState)) + loadingFailed(MediaPlayer::NetworkState::Empty); + } + break; + case GST_MESSAGE_CLOCK_LOST: + // This can only happen in PLAYING state and we should just + // get a new clock by moving back to PAUSED and then to + // PLAYING again. + // This can happen if the stream that ends in a sink that + // provides the current clock disappears, for example if + // the audio sink provides the clock and the audio stream + // is disabled. It also happens relatively often with + // HTTP adaptive streams when switching between different + // variants of a stream. + gst_element_set_state(m_pipeline.get(), GST_STATE_PAUSED); + gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); + break; + case GST_MESSAGE_LATENCY: + // Recalculate the latency, we don't need any special handling + // here other than the GStreamer default. + // This can happen if the latency of live elements changes, or + // for one reason or another a new live element is added or + // removed from the pipeline. + gst_bin_recalculate_latency(GST_BIN(m_pipeline.get())); + break; + case GST_MESSAGE_ELEMENT: +#if USE(GSTREAMER_MPEGTS) + if (GstMpegtsSection* section = gst_message_parse_mpegts_section(message)) { + processMpegTsSection(section); + gst_mpegts_section_unref(section); + } else +#endif + if (gst_structure_has_name(structure, "http-headers")) { + GST_DEBUG_OBJECT(pipeline(), "Processing HTTP headers: %" GST_PTR_FORMAT, structure); + if (const char* uri = gst_structure_get_string(structure, "uri")) { + URL url { String::fromLatin1(uri) }; + + if (url != m_url) { + GST_DEBUG_OBJECT(pipeline(), "Ignoring HTTP response headers for non-main URI."); + break; + } + } + + bool isRangeRequest = false; + GUniqueOutPtr requestHeaders; + if (gst_structure_get(structure, "request-headers", GST_TYPE_STRUCTURE, &requestHeaders.outPtr(), nullptr)) + isRangeRequest = gst_structure_has_field(requestHeaders.get(), "Range"); + + GST_DEBUG_OBJECT(pipeline(), "Is range request: %s", boolForPrinting(isRangeRequest)); + + GUniqueOutPtr responseHeaders; + if (gst_structure_get(structure, "response-headers", GST_TYPE_STRUCTURE, &responseHeaders.outPtr(), nullptr)) { + auto contentLengthHeaderName = httpHeaderNameString(HTTPHeaderName::ContentLength); + uint64_t contentLength = 0; + if (!gst_structure_get_uint64(responseHeaders.get(), contentLengthHeaderName.characters(), &contentLength)) { + // souphttpsrc sets a string for Content-Length, so + // handle it here, until we remove the webkit+ protocol + // prefix from webkitwebsrc. + if (const char* contentLengthAsString = gst_structure_get_string(responseHeaders.get(), contentLengthHeaderName.characters())) { + contentLength = g_ascii_strtoull(contentLengthAsString, nullptr, 10); + if (contentLength == G_MAXUINT64) + contentLength = 0; + } + } + if (!isRangeRequest) { + m_isLiveStream = !contentLength; + GST_INFO_OBJECT(pipeline(), "%s stream detected", m_isLiveStream.value_or(false) ? "Live" : "Non-live"); + updateDownloadBufferingFlag(); + } + } + } else if (gst_structure_has_name(structure, "webkit-network-statistics")) { + if (gst_structure_get(structure, "read-position", G_TYPE_UINT64, &m_networkReadPosition, "size", G_TYPE_UINT64, &m_httpResponseTotalSize, nullptr)) + GST_LOG_OBJECT(pipeline(), "Updated network read position %" G_GUINT64_FORMAT ", size: %" G_GUINT64_FORMAT, m_networkReadPosition, m_httpResponseTotalSize); + } else if (gst_structure_has_name(structure, "GstCacheDownloadComplete")) { + GST_INFO_OBJECT(pipeline(), "Stream is fully downloaded, stopping monitoring downloading progress."); + m_fillTimer.stop(); + m_bufferingPercentage = 100; + updateStates(); + } else if (gst_structure_has_name(structure, "webkit-web-src-has-eos")) { + GST_DEBUG_OBJECT(pipeline(), "WebKitWebSrc has EOS"); + m_hasWebKitWebSrcSentEOS = true; + } else + GST_DEBUG_OBJECT(pipeline(), "Unhandled element message: %" GST_PTR_FORMAT, structure); + break; + case GST_MESSAGE_TOC: + processTableOfContents(message); + break; + case GST_MESSAGE_STREAMS_SELECTED: { + if (m_isLegacyPlaybin) + break; + +#ifndef GST_DISABLE_DEBUG + GST_DEBUG_OBJECT(m_pipeline.get(), "Received STREAMS_SELECTED message selecting the following streams:"); + unsigned numStreams = gst_message_streams_selected_get_size(message); + for (unsigned i = 0; i < numStreams; i++) { + auto stream = adoptGRef(gst_message_streams_selected_get_stream(message, i)); + GST_DEBUG_OBJECT(pipeline(), "#%u %s %s", i, gst_stream_type_get_name(gst_stream_get_stream_type(stream.get())), gst_stream_get_stream_id(stream.get())); + } +#endif + GST_DEBUG_OBJECT(m_pipeline.get(), "Setting m_waitingForStreamsSelectedEvent to false."); + m_waitingForStreamsSelectedEvent = false; + + // Unfortunately, STREAMS_SELECTED messages from playbin3 are highly unreliable, often only including the audio + // stream or only the video stream when both are present and going to be played. + // Therefore, instead of reading the event data, we will just assume our previously requested selection was honored. + m_currentAudioStreamId = m_requestedAudioStreamId; + m_currentVideoStreamId = m_requestedVideoStreamId; + m_currentTextStreamId = m_requestedTextStreamId; + + // It's possible the user made a track switch before the initial STREAMS_SELECED. Now it's a good moment to + // request it being attended. Note that it's not possible to send a SELECT_STREAMS before the first + // STREAMS_SELECTED message because at that point the pipeline is not compeletely constructed. + playbin3SendSelectStreamsIfAppropriate(); + break; + } + case GST_MESSAGE_STREAM_START: { + // Real track id configuration in MSE is managed by AppendPipeline. In MediaStream we don't support native stream ids. + if (!m_isLegacyPlaybin) + break; + + notifyPlayerOfTrack(); + notifyPlayerOfTrack(); + notifyPlayerOfTrack(); + break; + } + default: + GST_DEBUG_OBJECT(pipeline(), "Unhandled GStreamer message type: %s", GST_MESSAGE_TYPE_NAME(message)); + break; + } +} + +void MediaPlayerPrivateGStreamer::processBufferingStats(GstMessage* message) +{ + GstBufferingMode mode; + gst_message_parse_buffering_stats(message, &mode, nullptr, nullptr, nullptr); + + int percentage; + gst_message_parse_buffering(message, &percentage); + + updateBufferingStatus(mode, percentage); +} + +void MediaPlayerPrivateGStreamer::updateMaxTimeLoaded(double percentage) +{ + MediaTime mediaDuration = durationMediaTime(); + if (!mediaDuration) + return; + + m_maxTimeLoaded = MediaTime(percentage * static_cast(toGstUnsigned64Time(mediaDuration)) / 100, GST_SECOND); + GST_DEBUG_OBJECT(pipeline(), "[Buffering] Updated maxTimeLoaded: %s", toString(m_maxTimeLoaded).utf8().data()); +} + +void MediaPlayerPrivateGStreamer::updateBufferingStatus(GstBufferingMode mode, double percentage) +{ + bool wasBuffering = m_isBuffering; + +#ifndef GST_DISABLE_GST_DEBUG + GUniquePtr modeString(g_enum_to_string(GST_TYPE_BUFFERING_MODE, mode)); + GST_DEBUG_OBJECT(pipeline(), "[Buffering] mode: %s, status: %f%%", modeString.get(), percentage); +#endif + + m_didDownloadFinish = percentage == 100; + + if (!m_didDownloadFinish) + m_isBuffering = true; + else + m_fillTimer.stop(); + + m_bufferingPercentage = percentage; + switch (mode) { + case GST_BUFFERING_STREAM: { + updateMaxTimeLoaded(percentage); + + m_bufferingPercentage = percentage; + if (m_didDownloadFinish || !wasBuffering) + updateStates(); + + break; + } + case GST_BUFFERING_DOWNLOAD: { + updateMaxTimeLoaded(percentage); + updateStates(); + break; + } + default: +#ifndef GST_DISABLE_GST_DEBUG + GST_DEBUG_OBJECT(pipeline(), "Unhandled buffering mode: %s", modeString.get()); +#endif + break; + } +} + +#if USE(GSTREAMER_MPEGTS) +void MediaPlayerPrivateGStreamer::processMpegTsSection(GstMpegtsSection* section) +{ + ASSERT(section); + + if (section->section_type == GST_MPEGTS_SECTION_PMT) { + const GstMpegtsPMT* pmt = gst_mpegts_section_get_pmt(section); + m_metadataTracks.clear(); + for (unsigned i = 0; i < pmt->streams->len; ++i) { + const GstMpegtsPMTStream* stream = static_cast(g_ptr_array_index(pmt->streams, i)); + if (stream->stream_type == 0x05 || stream->stream_type >= 0x80) { + AtomString pid = AtomString::number(stream->pid); + RefPtr track = InbandMetadataTextTrackPrivateGStreamer::create( + InbandTextTrackPrivate::Kind::Metadata, InbandTextTrackPrivate::CueFormat::Data, pid); + + // 4.7.10.12.2 Sourcing in-band text tracks + // If the new text track's kind is metadata, then set the text track in-band metadata track dispatch + // type as follows, based on the type of the media resource: + // Let stream type be the value of the "stream_type" field describing the text track's type in the + // file's program map section, interpreted as an 8-bit unsigned integer. Let length be the value of + // the "ES_info_length" field for the track in the same part of the program map section, interpreted + // as an integer as defined by the MPEG-2 specification. Let descriptor bytes be the length bytes + // following the "ES_info_length" field. The text track in-band metadata track dispatch type must be + // set to the concatenation of the stream type byte and the zero or more descriptor bytes bytes, + // expressed in hexadecimal using uppercase ASCII hex digits. + StringBuilder inbandMetadataTrackDispatchType; + inbandMetadataTrackDispatchType.append(hex(stream->stream_type, 2)); + for (unsigned j = 0; j < stream->descriptors->len; ++j) { + const GstMpegtsDescriptor* descriptor = static_cast(g_ptr_array_index(stream->descriptors, j)); + for (unsigned k = 0; k < descriptor->length; ++k) + inbandMetadataTrackDispatchType.append(hex(descriptor->data[k], 2)); + } + track->setInBandMetadataTrackDispatchType(inbandMetadataTrackDispatchType.toAtomString()); + + m_metadataTracks.add(pid, track); + if (RefPtr player = m_player.get()) + player->addTextTrack(*track); + } + } + } else { + AtomString pid = AtomString::number(section->pid); + RefPtr track = m_metadataTracks.get(pid); + if (!track) + return; + + GRefPtr data = gst_mpegts_section_get_data(section); + gsize size; + const void* bytes = g_bytes_get_data(data.get(), &size); + + track->addDataCue(currentMediaTime(), currentMediaTime(), bytes, size); + } +} +#endif + +void MediaPlayerPrivateGStreamer::processTableOfContents(GstMessage* message) +{ + RefPtr player = m_player.get(); + + if (player && m_chaptersTrack) + player->removeTextTrack(*m_chaptersTrack); + + m_chaptersTrack = InbandMetadataTextTrackPrivateGStreamer::create(InbandTextTrackPrivate::Kind::Chapters, InbandTextTrackPrivate::CueFormat::Generic); + if (player) + player->addTextTrack(*m_chaptersTrack); + + GRefPtr toc; + gboolean updated; + gst_message_parse_toc(message, &toc.outPtr(), &updated); + ASSERT(toc); + + for (GList* i = gst_toc_get_entries(toc.get()); i; i = i->next) + processTableOfContentsEntry(static_cast(i->data)); +} + +void MediaPlayerPrivateGStreamer::processTableOfContentsEntry(GstTocEntry* entry) +{ + ASSERT(entry); + + auto cue = InbandGenericCue::create(); + + gint64 start = -1, stop = -1; + gst_toc_entry_get_start_stop_times(entry, &start, &stop); + + uint32_t truncatedGstSecond = static_cast(GST_SECOND); + if (start != -1) + cue->setStartTime(MediaTime(static_cast(start), truncatedGstSecond)); + if (stop != -1) + cue->setEndTime(MediaTime(static_cast(stop), truncatedGstSecond)); + + GstTagList* tags = gst_toc_entry_get_tags(entry); + if (tags) { + gchar* title = nullptr; + gst_tag_list_get_string(tags, GST_TAG_TITLE, &title); + if (title) { + cue->setContent(String::fromUTF8(title)); + g_free(title); + } + } + + m_chaptersTrack->addGenericCue(cue); + + for (GList* i = gst_toc_entry_get_sub_entries(entry); i; i = i->next) + processTableOfContentsEntry(static_cast(i->data)); +} + +void MediaPlayerPrivateGStreamer::configureElement(GstElement* element) +{ +#if PLATFORM(BROADCOM) || USE(WESTEROS_SINK) || PLATFORM(AMLOGIC) || PLATFORM(REALTEK) + configureElementPlatformQuirks(element); +#endif + + GUniquePtr elementName(gst_element_get_name(element)); + auto elementClass = makeString(gst_element_get_metadata(element, GST_ELEMENT_METADATA_KLASS)); + auto classifiers = elementClass.split('/'); + + // In GStreamer 1.20 and older urisourcebin mishandles source elements with dynamic pads. This + // is not an issue in 1.22. Streams parsing is not needed for MediaStream cases because we do it + // upfront for incoming WebRTC MediaStreams. It is however needed for MSE, otherwise decodebin3 + // might not auto-plug hardware decoders. + if (webkitGstCheckVersion(1, 22, 0) && g_str_has_prefix(elementName.get(), "urisourcebin") && (isMediaSource() || isMediaStreamPlayer())) + g_object_set(element, "use-buffering", FALSE, "parse-streams", !isMediaStreamPlayer(), nullptr); + + // In case of playbin3 with