From 844c133017013838341cfa26fc41bcedc3f0b8f2 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Tue, 24 Sep 2013 22:12:12 +0200 Subject: [PATCH] Initial player integration using Qt Multimedia --- README | 1 + index.qml | 14 +++++++- index_sailfish.qml | 28 +++++++++++++++ main.py | 9 +++++ qml/ButtonRow.qml | 45 +++++++++++++++++++++++ qml/EpisodeDetail.qml | 11 ++++++ qml/EpisodesPage.qml | 1 + qml/Main.qml | 18 +++------- qml/PSlider.qml | 53 +++++++++++++++++++++++++++ qml/Player.qml | 37 +++++++++++++++++++ qml/PlayerPage.qml | 83 +++++++++++++++++++++++++++++++++++++++++++ qml/PodcastsPage.qml | 4 +++ qml/StartPage.qml | 16 +++++++++ 13 files changed, 306 insertions(+), 14 deletions(-) create mode 100644 index_sailfish.qml create mode 100644 qml/ButtonRow.qml create mode 100644 qml/PSlider.qml create mode 100644 qml/Player.qml create mode 100644 qml/PlayerPage.qml diff --git a/README b/README index 5ccf4a1..d40b265 100644 --- a/README +++ b/README @@ -10,6 +10,7 @@ Dependencies: Package Min.Version URL ------------------------------------------------------------ Qt 5.0.2 http://qt-project.org/ +Qt Multimedia 5.0.2 http://qt-project.org/ Python 3.3.0 http://python.org/ PyOtherSide 1.0.0 http://thp.io/2011/pyotherside/ gPodder 4.0.0 http://gpodder.org/ diff --git a/index.qml b/index.qml index d59aaf2..c2303aa 100644 --- a/index.qml +++ b/index.qml @@ -21,9 +21,21 @@ import QtQuick 2.0 import 'qml' Rectangle { - color: 'black' + color: '#336688' + width: 480 height: 800 + Image { + anchors.fill: parent + source: 'qml/images/mask.png' + } + + Image { + anchors.fill: parent + source: 'qml/images/noise.png' + fillMode: Image.Tile + } + Main {} } diff --git a/index_sailfish.qml b/index_sailfish.qml new file mode 100644 index 0000000..c87b406 --- /dev/null +++ b/index_sailfish.qml @@ -0,0 +1,28 @@ +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import 'qml' + +ApplicationWindow { + initialPage: Page { + Main {} + } +} diff --git a/main.py b/main.py index ca2d293..105aad9 100644 --- a/main.py +++ b/main.py @@ -202,6 +202,14 @@ class gPotherSide: self._checking_for_new_episodes = False pyotherside.send('refreshing', False) + def play_episode(self, episode_id): + episode = self._get_episode_by_id(episode_id) + return { + 'source': episode.local_filename(False) + if episode.was_downloaded(and_exists=True) + else episode.url, + } + def show_episode(self, episode_id): episode = self._get_episode_by_id(episode_id) return { @@ -218,6 +226,7 @@ pyotherside.send('hello', gpodder.__version__, gpodder.__copyright__) load_podcasts = gpotherside.load_podcasts load_episodes = gpotherside.load_episodes show_episode = gpotherside.show_episode +play_episode = gpotherside.play_episode subscribe = gpotherside.subscribe unsubscribe = gpotherside.unsubscribe check_for_episodes = gpotherside.check_for_episodes diff --git a/qml/ButtonRow.qml b/qml/ButtonRow.qml new file mode 100644 index 0000000..a65fd80 --- /dev/null +++ b/qml/ButtonRow.qml @@ -0,0 +1,45 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Row { + id: buttonRow + property var model + + height: 100 * pgst.scalef + + Repeater { + id: repeater + model: buttonRow.model + + delegate: ButtonArea { + height: buttonRow.height + width: buttonRow.width / repeater.count + onClicked: buttonRow.model[index].clicked() + + PLabel { + anchors.centerIn: parent + text: modelData.label + } + } + } +} + diff --git a/qml/EpisodeDetail.qml b/qml/EpisodeDetail.qml index fed4fed..a58e9cd 100644 --- a/qml/EpisodeDetail.qml +++ b/qml/EpisodeDetail.qml @@ -52,6 +52,17 @@ SlidePage { title: detailPage.title } + ButtonArea { + width: detailPage.width + height: 100 * pgst.scalef + onClicked: player.playbackEpisode(detailPage.episode_id) + + PLabel { + anchors.centerIn: parent + text: 'Play' + } + } + PLabel { id: label width: parent.width * .8 diff --git a/qml/EpisodesPage.qml b/qml/EpisodesPage.qml index 70cacab..47810ad 100644 --- a/qml/EpisodesPage.qml +++ b/qml/EpisodesPage.qml @@ -44,6 +44,7 @@ SlidePage { PullMenuItem { source: 'images/play.png' onClicked: { + pgst.loadPage('PlayerPage.qml'); episodesPage.unPull(); } } diff --git a/qml/Main.qml b/qml/Main.qml index f5c96df..30b550f 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -21,25 +21,13 @@ import QtQuick 2.0 import io.thp.pyotherside 1.0 -Rectangle { +Item { id: pgst property bool ready: false property real scalef: width / 480 anchors.fill: parent - color: '#336688' - - Image { - anchors.fill: parent - source: 'images/mask.png' - } - - Image { - anchors.fill: parent - source: 'images/noise.png' - fillMode: Image.Tile - } function update(page, x) { var index = -1; @@ -97,6 +85,10 @@ Rectangle { } } + Player { + id: player + } + PBusyIndicator { anchors.centerIn: parent visible: !pgst.ready diff --git a/qml/PSlider.qml b/qml/PSlider.qml new file mode 100644 index 0000000..a4144d4 --- /dev/null +++ b/qml/PSlider.qml @@ -0,0 +1,53 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +Rectangle { + id: slider + + property real value + property real min: 0.0 + property real max: 1.0 + + signal valueChangeRequested(real newValue) + + color: '#aa000000' + + height: 50 * pgst.scalef + + MouseArea { + anchors.fill: parent + onClicked: slider.valueChangeRequested(min + (max - min) * (mouse.x / width)) + } + + Rectangle { + height: parent.height * 0.9 + width: height + + color: '#aaffffff' + + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: parent.width * (parent.value - parent.min) / (parent.max - parent.min) + } + } +} diff --git a/qml/Player.qml b/qml/Player.qml new file mode 100644 index 0000000..cc7c3b7 --- /dev/null +++ b/qml/Player.qml @@ -0,0 +1,37 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 +import QtMultimedia 5.0 + +MediaPlayer { + id: player + + property int episode + property var queue: ([]) + + function playbackEpisode(episode_id) { + player.episode = episode_id; + py.call('main.play_episode', [episode_id], function (episode) { + player.source = episode.source; + player.play(); + }); + } +} diff --git a/qml/PlayerPage.qml b/qml/PlayerPage.qml new file mode 100644 index 0000000..8928099 --- /dev/null +++ b/qml/PlayerPage.qml @@ -0,0 +1,83 @@ + +/** + * + * gPodder QML UI Reference Implementation + * Copyright (c) 2013, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +import QtQuick 2.0 + +import 'constants.js' as Constants + +SlidePage { + id: playerPage + + property string episodeTitle + + Component.onCompleted: { + py.call('main.show_episode', [player.episode], function (episode) { + playerPage.episodeTitle = episode.title; + }); + } + + Flickable { + id: flickable + anchors.fill: parent + + contentWidth: column.width + contentHeight: column.height + column.spacing + + Column { + id: column + + width: playerPage.width + spacing: 10 * pgst.scalef + + SlidePageHeader { + title: 'Now playing' + } + + ButtonRow { + width: playerPage.width + model: [ + { label: 'Play', clicked: function() { + player.play(); + }}, + { label: 'Pause', clicked: function() { + player.pause(); + }}, + { label: 'Details', clicked: function() { + pgst.loadPage('EpisodeDetail.qml', { + episode_id: player.episode, + title: playerPage.episodeTitle + }); + }} + ] + } + + PSlider { + width: playerPage.width + value: player.playbackRate + min: 0.5 + max: 3.0 + onValueChangeRequested: { + player.playbackRate = newValue + value = player.playbackRate + } + } + } + } +} diff --git a/qml/PodcastsPage.qml b/qml/PodcastsPage.qml index f1c625b..d64bb8d 100644 --- a/qml/PodcastsPage.qml +++ b/qml/PodcastsPage.qml @@ -68,6 +68,10 @@ SlidePage { PullMenu { PullMenuItem { source: 'images/play.png' + onClicked: { + pgst.loadPage('PlayerPage.qml'); + podcastsPage.unPull(); + } } PullMenuItem { diff --git a/qml/StartPage.qml b/qml/StartPage.qml index 7dbbba7..c5d2979 100644 --- a/qml/StartPage.qml +++ b/qml/StartPage.qml @@ -181,6 +181,22 @@ SlidePage { } } + ButtonArea { + onClicked: pgst.loadPage('PlayerPage.qml'); + + anchors { + left: recommendationsPane.left + right: recommendationsPane.right + } + + height: 100 * pgst.scalef + + PLabel { + anchors.centerIn: parent + text: 'Now playing' + } + } + ButtonArea { onClicked: pgst.loadPage('Settings.qml');