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