youtubeview.qml Example File | Qt WebKit Examples 5.5

- Qt 5.5
- Qt WebKit Examples
- YouTube View Example
- youtubeview.qml Example File
- Qt 5.5.1 Reference Documentation

# youtubeview.qml Example File

webkitqml/youtubeview/youtubeview.qml

    /****************************************************************************
    **
    ** Copyright (C) 2015 The Qt Company Ltd.
    ** Contact: http://www.qt.io/licensing/
    **
    ** This file is part of the examples of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** You may use this file under the terms of the BSD license as follows:
    **
    ** "Redistribution and use in source and binary forms, with or without
    ** modification, are permitted provided that the following conditions are
    ** met:
    **   * Redistributions of source code must retain the above copyright
    **     notice, this list of conditions and the following disclaimer.
    **   * Redistributions in binary form must reproduce the above copyright
    **     notice, this list of conditions and the following disclaimer in
    **     the documentation and/or other materials provided with the
    **     distribution.
    **   * Neither the name of The Qt Company Ltd nor the names of its
    **     contributors may be used to endorse or promote products derived
    **     from this software without specific prior written permission.
    **
    **
    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    ** DATA, OR 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."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/

    import QtQuick 2.0
    import QtWebKit 3.0
    import QtQuick.XmlListModel 2.0
    import "qrc:/shared" as Shared
    import "qrc:/content" as Content

    Rectangle {
        id: container
        width: 850
        height: 480
        color: "black"
        focus: true

        property QtObject videoStatus: QtObject {
            property int initial: -1
            property int ready: 0
            property int playing: 1
            property int paused: 2
        }

        QtObject {
            id: currentVideo
            property string vId: ""
            property string title: ""
            property int status: videoStatus.initial
        }

        readonly property int padding: 20

        Rectangle {
            id: content
            anchors.fill: parent
            color: "black"

            WebView {
                id: webView
                anchors.fill: parent
                opacity: 0

                url: "qrc:///content/player.html?" + currentVideo.vId

                Behavior on opacity { NumberAnimation { duration: 200 } }

                onLoadingChanged: {
                    switch (loadRequest.status)
                    {
                    case WebView.LoadSucceededStatus:
                        opacity = 1
                        return
                    case WebView.LoadStartedStatus:
                    case WebView.LoadStoppedStatus:
                        break
                    case WebView.LoadFailedStatus:
                        topInfo.text = "Failed to load the requested video"
                        break
                    }
                    opacity = 0
                }
                onTitleChanged: {
                    currentVideo.status = 1 * title
                    if (title == videoStatus.paused || title == videoStatus.ready)
                        panel.state = "list"
                    else if (title == videoStatus.playing)
                        panel.state = "hidden"
                }
            }

            Content.YouTubeDialog {
                id: presetDialog
                anchors.fill: parent
                visible: false
                onPresetClicked: {
                    model.userName = name
                    model.startIndex = 1
                    panel.state = "list"
                    searchBinding.when = false
                    presetsBinding.when = true
                    model.reload()
                }
            }
        }

        Rectangle {
            id: panel
            height: 100
            color: "black";
            state: "list"

            Behavior on y { NumberAnimation { duration: 200 } }
            Behavior on height { NumberAnimation { duration: 200 } }
            Behavior on opacity { NumberAnimation { duration: 400 } }

            Binding { id: presetsBinding; target: model; property: "source"; value: model.usersSource; when: false }
            Binding { id: searchBinding; target: model; property: "source"; value: model.searchSource; when: false }

            anchors {
                left: container.left
                right: container.right
            }

            states: [
                State {
                    name: "search"
                    PropertyChanges { target: panel; color: "black"; opacity: 0.8; y: -height + topInfo.height + searchPanel.height + button.height }
                    PropertyChanges { target: listView; visible: false }
                    PropertyChanges { target: searchPanel; opacity: 0.8 }
                    PropertyChanges { target: hideTimer; running: false }
                    PropertyChanges { target: presetDialog; visible: true }
                },

                State {
                    name: "list"
                    PropertyChanges { target: panel; color: "black"; opacity: 0.8; y: 0 }
                    PropertyChanges { target: listView; visible: true; focus: true }
                    PropertyChanges { target: searchPanel; visible: false }
                    PropertyChanges { target: listView; visible: true }
                },

                State {
                    name: "hidden"
                    PropertyChanges { target: panel; color: "gray"; opacity: 0.2; y: -height }
                }
            ]

            Timer {
                id: hideTimer
                interval: 3000
                repeat: false
                onTriggered: panel.state = "hidden"
            }

            ListView {
                id: listView
                orientation: "Horizontal"

                anchors {
                    top: panel.top
                    bottom: button.top
                    left: panel.left
                    right: panel.right
                }

                focus: true
                model: model

                header: Component {
                    Rectangle {
                        visible: model.startIndex != 1 && model.status == XmlListModel.Ready
                        color: "black"
                        anchors.verticalCenter: parent.verticalCenter
                        width: height
                        height: visible ? listView.contentItem.height : 0
                        Image { anchors.centerIn: parent; width: 50; height: 50; source: "qrc:/shared/images/less.png" }
                        MouseArea {
                            anchors.fill: parent
                            onClicked: model.requestLess()
                        }
                    }
                }

                footer: Component {
                    Rectangle {
                        visible: model.totalResults > model.endIndex && model.status == XmlListModel.Ready
                        color: "black"
                        anchors.verticalCenter: parent.verticalCenter
                        width: height
                        height: visible ? listView.contentItem.height : 0
                        Image { anchors.centerIn: parent; width: 50; height: 50; source: "qrc:/shared/images/more.png" }
                        MouseArea {
                            anchors.fill: parent
                            onClicked: model.requestMore()
                        }
                    }
                }

                delegate: Component {
                    Image {
                        source: thumbnail
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                currentVideo.vId = id
                                currentVideo.title = title
                            }
                        }
                        Component.onCompleted: {
                            if (currentVideo.title == "") {
                                currentVideo.vId = id
                                currentVideo.title = title
                            }
                        }
                    }
                }

                onDraggingChanged: {
                    if (dragging)
                        hideTimer.stop()
                    else if (currentVideo.status == videoStatus.playing)
                        hideTimer.start()
                }
            }

            Shared.LoadIndicator {
                anchors.fill: parent
                color: "black"
                running: panel.state == "list" && model.status != XmlListModel.Ready
            }

            Rectangle {
                id: searchPanel
                Behavior on opacity { NumberAnimation { duration: 400 } }

                height: searchField.height + container.padding

                anchors {
                    left: parent.left
                    right: parent.right
                    bottom: button.top
                }

                opacity: 0
                color: "black"

                gradient: Gradient {
                    GradientStop {
                        position: 0.0
                        color: "grey"
                    }
                    GradientStop {
                        position: 1.0
                        color: "black"
                    }
                }

                Rectangle {
                    id: searchField
                    color: "white"
                    radius: 2
                    anchors.centerIn: parent
                    width: 220
                    border.color: "black"
                    border.width: 2
                    height: input.height + container.padding
                    TextInput {
                        id: input
                        color: "black"
                        anchors.centerIn: parent
                        horizontalAlignment: TextInput.AlignHCenter
                        font.capitalization: Font.AllLowercase
                        maximumLength: 30
                        cursorVisible: true
                        focus: parent.visible
                        text: "movie trailers"
                        Keys.onPressed: {
                            if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) {
                                model.startIndex = 1
                                panel.state = "list"
                                presetsBinding.when = false
                                searchBinding.when = true
                                model.reload()
                            }
                        }
                    }
                    MouseArea {
                        anchors.fill: parent
                        onPressed: input.focus = true
                    }
                }
            }

            Shared.Button {
                id: button
                buttonHeight: container.padding
                buttonWidth: container.width
                fontSize: 8

                visible: panel.state != "hidden"

                anchors {
                    bottom: parent.bottom
                    left: parent.left
                    right: parent.right
                }

                states: [
                    State {
                        name: "search"
                        PropertyChanges { target: button; text: "Press to switch back to the video list" }
                    },

                    State {
                        name: "list"
                        PropertyChanges { target: button; text: "Press to search for videos" }
                    }
                ]

                state: panel.state

                onClicked: {
                    if (panel.state == "search")
                        panel.state = "list"
                    else
                        panel.state = "search"
                }
            }
        }

        Rectangle {
            height: 10
            color: "black"
            opacity: (panel.state == "hidden") ? 0 : 0.8

            Behavior on opacity { NumberAnimation { duration: 200 } }

            anchors {
                top: container.top
                left: container.left
                right: container.right
            }

            Text {
                id: topInfo
                color: "white"
                font.pointSize: 8
                anchors.centerIn: parent
                Binding on text {
                    value: "Results " + model.startIndex + " through " + ((model.endIndex > model.totalResults) ? model.totalResults : model.endIndex) + " out of " + model.totalResults
                    when: model.status == XmlListModel.Ready && panel.state == "list" && model.count
                }
                Binding on text {
                    value: "No results found.";
                    when: model.state == XmlListModel.Ready && !model.count
                }
                Binding on text {
                    value: "Search for videos"
                    when: panel.state == "search"
                }
            }
        }

        Rectangle {
            height: container.padding
            color: "black"
            opacity: (panel.state == "hidden") ? 0.2 : 0.8

            Behavior on opacity { NumberAnimation { duration: 200 } }

            anchors {
                top: panel.bottom
                left: container.left
                right: container.right
            }

            Text {
                id: bottomInfo
                color: "white"
                font.weight: Font.DemiBold
                font.pointSize: 8
                anchors.centerIn: parent
                text: {
                    if (panel.state == "search")
                        return "Choose from preset video streams"
                    else
                        return currentVideo.title
                }
            }

            MouseArea {
                // Responsible for showing and hiding the thumbnail list.
                anchors.fill: parent
                onPressed: {
                    if (panel.state != "list") {
                        panel.state = "list"
                        if (currentVideo.status == videoStatus.playing)
                            hideTimer.restart()
                    } else
                        panel.state = "hidden"
                }
            }
        }

        XmlListModel {
            id: model

            property int totalResults: 0
            property int itemsPerPage: 0
            property int startIndex: 1
            property int endIndex: itemsPerPage + startIndex - 1

            property string userName: "trailers"
            property string baseUrl: "https://gdata.youtube.com/feeds/api"
            property string defaultQuery: "alt=rss&orderby=published&v=2&start-index=" + startIndex
            property string searchSource: baseUrl + "/videos?" + defaultQuery + "&q=\'" + input.text + "\'"
            property string usersSource: baseUrl + "/users/" + userName + "/uploads?" + defaultQuery

            function requestMore() {
                startIndex += itemsPerPage
                reload()
            }

            function requestLess() {
                startIndex -= itemsPerPage
                reload()
            }

            onSourceChanged: {
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function(){
                    if (xhr.readyState == XMLHttpRequest.DONE) {
                        if (xhr.status != 200) {
                            console.log("Something went wrong, received HTTP status code " + xhr.status);
                            return;
                        }
                        var doc = xhr.responseXML.documentElement;
                        for (var i = 0; i < doc.childNodes.length; ++i) {
                            var child = doc.childNodes[i];
                            for (var j = 0; j < child.childNodes.length; ++j) {
                                if (child.childNodes[j].nodeName == "itemsPerPage")
                                    itemsPerPage = child.childNodes[j].childNodes[0].nodeValue;
                                if (child.childNodes[j].nodeName == "totalResults")
                                    totalResults = child.childNodes[j].childNodes[0].nodeValue;
                            }
                        }
                    }
                }
                xhr.open("GET", source);
                xhr.send();
            }

            namespaceDeclarations: "declare namespace media='http://search.yahoo.com/mrss/';declare namespace yt='http://gdata.youtube.com/schemas/2007';"

            source: usersSource
            query: "/rss/channel/item"

            XmlRole { name: "id"; query: "media:group/yt:videoid/string()"}
            XmlRole { name: "title"; query: "media:group/media:title/string()" }
            XmlRole { name: "thumbnail"; query: "media:group/media:thumbnail[1]/@url/string()" }
            XmlRole { name: "thumbnailHeight"; query: "media:group/media:thumbnail[1]/@height/number()" }
        }
    }