Initial player integration using Qt Multimedia

This commit is contained in:
Thomas Perl 2013-09-24 22:12:12 +02:00
parent a333def9c1
commit 844c133017
13 changed files with 306 additions and 14 deletions

1
README
View file

@ -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/

View file

@ -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 {}
}

28
index_sailfish.qml Normal file
View file

@ -0,0 +1,28 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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 {}
}
}

View file

@ -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

45
qml/ButtonRow.qml Normal file
View file

@ -0,0 +1,45 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}
}
}

View file

@ -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

View file

@ -44,6 +44,7 @@ SlidePage {
PullMenuItem {
source: 'images/play.png'
onClicked: {
pgst.loadPage('PlayerPage.qml');
episodesPage.unPull();
}
}

View file

@ -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

53
qml/PSlider.qml Normal file
View file

@ -0,0 +1,53 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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)
}
}
}

37
qml/Player.qml Normal file
View file

@ -0,0 +1,37 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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();
});
}
}

83
qml/PlayerPage.qml Normal file
View file

@ -0,0 +1,83 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2013, Thomas Perl <m@thp.io>
*
* 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
}
}
}
}
}

View file

@ -68,6 +68,10 @@ SlidePage {
PullMenu {
PullMenuItem {
source: 'images/play.png'
onClicked: {
pgst.loadPage('PlayerPage.qml');
podcastsPage.unPull();
}
}
PullMenuItem {

View file

@ -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');