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()" }
    }
}

© 2016 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.