on xenial help sign in
Initial playlist support
commit596942868fda7ef2055bd187ea66e8ccb93b9ad9 authorAlbert Astals Cid <albert.astals@canonical.com> dateTue 30 Jun 2015, 16:57 (11 years ago) parent68ddbaf
9 files changed+300−114
plugins/Dash/CardCreator.js+2−1 plugins/Dash/DashAudioPlayer.qml+38−4 tests/mocks/QtMultimedia/CMakeLists.txt+1 tests/mocks/QtMultimedia/audio.cpp+37−10 tests/mocks/QtMultimedia/audio.h+9−2 tests/mocks/QtMultimedia/declarativeplaylist.cpp+61 tests/mocks/QtMultimedia/declarativeplaylist.h+53 tests/mocks/QtMultimedia/plugin.cpp+2 tests/plugins/Dash/cardcreator/8.res+97−97
plugins/Dash/CardCreator.js+2−1
@@ -119,6 +119,7 @@ var kAudioButtonCode = 'AbstractButton { \n\
119119 width: %2; \n\
120120 height: %3; \n\
121121 readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || ""; \n\
122+ readonly property url playlist: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null; \n\
122123 UbuntuShape { \n\
123124 anchors.fill: parent; \n\
124125 visible: parent.pressed; \n\
@@ -139,7 +140,7 @@ var kAudioButtonCode = 'AbstractButton { \n\
139140 DashAudioPlayer.play(); \n\
140141 } \n\
141142 } else { \n\
142− DashAudioPlayer.playSource(source); \n\
143+ DashAudioPlayer.playSource(source, playlist); \n\
143144 } \n\
144145 } \n\
145146 onPressAndHold: { \n\
plugins/Dash/DashAudioPlayer.qml+38−4
@@ -27,14 +27,44 @@ QtObject {
2727 readonly property alias position: audio.position
2828
2929 function isCurrentSource(source) {
30− return source != "" && AudioUrlComparer.compare(source, audio.source);
30+ if (source != "") {
31+ if (audio.playlist) {
32+ return AudioUrlComparer.compare(source, audio.playlist.currentSource);
33+ } else {
34+ return AudioUrlComparer.compare(source, audio.source);
35+ }
36+ }
37+ return false;
3138 }
3239
33− function playSource(newSource) {
40+ function playSource(newSource, newPlaylist) {
3441 stop();
3542 // Make sure we change the source, even if two items point to the same uri location
3643 audio.source = "";
37− audio.source = newSource;
44+ audio.playlist = null;
45+ if (newPlaylist) {
46+ playlist.clear();
47+ // Look for newSource in newPlaylist
48+ var sourceIndex = -1;
49+ for (var i in newPlaylist) {
50+ if (AudioUrlComparer.compare(newSource, newPlaylist[i])) {
51+ sourceIndex = i;
52+ break;
53+ }
54+ }
55+ if (sourceIndex === -1 && newSource != "") {
56+ // If the playing song is not in the playlist, add it
57+ playlist.addSource(newSource);
58+ sourceIndex = 0;
59+ }
60+ for (var i in newPlaylist) {
61+ playlist.addSource(newPlaylist[i]);
62+ }
63+ playlist.currentIndex = sourceIndex;
64+ audio.playlist = playlist;
65+ } else {
66+ audio.source = newSource;
67+ }
3868 play();
3969 }
4070
@@ -50,12 +80,16 @@ QtObject {
5080 audio.pause();
5181 }
5282
53− property QtObject d: Audio {
83+ property QtObject audio: Audio {
5484 id: audio
5585 objectName: "audio"
5686
5787 onErrorStringChanged: console.warn("Dash Audio player error:", errorString)
5888 }
89+ property QtObject playlist: Playlist {
90+ id: playlist
91+ objectName: "playlist"
92+ }
5993
6094 function lengthToString(s) {
6195 if (typeof(s) !== "number" || s < 0) return "";
tests/mocks/QtMultimedia/CMakeLists.txt+1
@@ -1,6 +1,7 @@
11add_library(QtMultimedia-qml MODULE
22 plugin.cpp
33 audio.cpp
4+ declarativeplaylist.cpp
45 )
56
67qt5_use_modules(QtMultimedia-qml Qml)
tests/mocks/QtMultimedia/audio.cpp+37−10
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -20,11 +20,18 @@
2020
2121Audio::Audio(QObject* parent):
2222 QObject(parent),
23− m_playbackState(StoppedState)
23+ m_playbackState(StoppedState),
24+ m_playlist(0)
2425{
2526 qsrand(time(nullptr));
2627 m_timer.setInterval(1000);
2728 connect(&m_timer, SIGNAL(timeout()), SLOT(timerEvent()));
29+
30+ m_position = 0;
31+ Q_EMIT positionChanged(m_position);
32+
33+ m_duration = (qrand() % 20000) + 10000;
34+ Q_EMIT durationChanged(m_duration);
2835}
2936
3037QUrl Audio::source() const
@@ -34,16 +41,20 @@ QUrl Audio::source() const
3441
3542void Audio::setSource(const QUrl &source)
3643{
37− if (m_source != source) {
38− m_source = source;
39− Q_EMIT sourceChanged(source);
44+ if (source == m_source && m_playlist == NULL)
45+ return;
4046
41− m_position = 0;
42− Q_EMIT positionChanged(m_position);
47+ m_playlist = NULL;
48+ Q_EMIT playlistChanged();
4349
44− m_duration = (qrand() % 20000) + 10000;
45− Q_EMIT durationChanged(m_duration);
46− }
50+ m_source = source;
51+ Q_EMIT sourceChanged(source);
52+
53+ m_position = 0;
54+ Q_EMIT positionChanged(m_position);
55+
56+ m_duration = (qrand() % 20000) + 10000;
57+ Q_EMIT durationChanged(m_duration);
4758}
4859
4960Audio::PlaybackState Audio::playbackState() const
@@ -115,3 +126,19 @@ void Audio::setAudioRole(Audio::AudioRole audioRole)
115126{
116127 Q_UNUSED(audioRole);
117128}
129+
130+DeclarativePlaylist *Audio::playlist() const
131+{
132+ return m_playlist;
133+}
134+
135+void Audio::setPlaylist(DeclarativePlaylist *playlist)
136+{
137+ if (playlist == m_playlist && m_source.isEmpty())
138+ return;
139+
140+ m_source.clear();
141+ Q_EMIT sourceChanged(m_source);
142+ m_playlist = playlist;
143+ Q_EMIT playlistChanged();
144+}
tests/mocks/QtMultimedia/audio.h+9−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2012,2013 Canonical, Ltd.
2+ * Copyright (C) 2012, 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 *
16− * Authors: Michael Zanetti <michael.zanetti@canonical.com>
1716 */
1817
1918#ifndef MOCK_AUDIO_H
@@ -23,6 +22,8 @@
2322#include <QUrl>
2423#include <QTimer>
2524
25+class DeclarativePlaylist;
26+
2627class Audio: public QObject
2728{
2829 Q_OBJECT
@@ -34,6 +35,7 @@ class Audio: public QObject
3435 Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
3536 Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
3637 Q_PROPERTY(AudioRole audioRole READ audioRole WRITE setAudioRole)
38+ Q_PROPERTY(DeclarativePlaylist *playlist READ playlist WRITE setPlaylist NOTIFY playlistChanged)
3739public:
3840 enum PlaybackState {
3941 PlayingState,
@@ -53,6 +55,9 @@ public:
5355 QUrl source() const;
5456 void setSource(const QUrl &source);
5557
58+ DeclarativePlaylist *playlist() const;
59+ void setPlaylist(DeclarativePlaylist *playlist);
60+
5661 PlaybackState playbackState() const;
5762
5863 int position() const;
@@ -70,6 +75,7 @@ public Q_SLOTS:
7075 void stop();
7176
7277Q_SIGNALS:
78+ void playlistChanged();
7379 void sourceChanged(const QUrl &source);
7480 void playbackStateChanged(PlaybackState playbackState);
7581 void positionChanged(int position);
@@ -85,6 +91,7 @@ private:
8591 QTimer m_timer;
8692 int m_position;
8793 int m_duration;
94+ DeclarativePlaylist *m_playlist;
8895};
8996
9097#endif
tests/mocks/QtMultimedia/declarativeplaylist.cppnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ */
17+
18+#include "declarativeplaylist.h"
19+
20+DeclarativePlaylist::DeclarativePlaylist(QObject *parent)
21+ : QObject(parent)
22+ , m_currentIndex(-1)
23+{
24+}
25+
26+QUrl DeclarativePlaylist::currentSource() const
27+{
28+ return source(currentIndex());
29+}
30+
31+int DeclarativePlaylist::currentIndex() const
32+{
33+ return m_currentIndex;
34+}
35+
36+void DeclarativePlaylist::setCurrentIndex(int index)
37+{
38+ if (currentIndex() == index)
39+ return;
40+
41+ m_currentIndex = index;
42+ Q_EMIT currentIndexChanged();
43+}
44+
45+QUrl DeclarativePlaylist::source(int index) const
46+{
47+ return m_medias[index];
48+}
49+
50+bool DeclarativePlaylist::addSource(const QUrl &source)
51+{
52+ m_medias << source;
53+ return true;
54+}
55+
56+
57+bool DeclarativePlaylist::clear()
58+{
59+ m_medias.clear();
60+ return true;
61+}
tests/mocks/QtMultimedia/declarativeplaylist.hnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ */
17+
18+#ifndef DECLARATIVEPLAYLIST_H
19+#define DECLARATIVEPLAYLIST_H
20+
21+#include <QObject>
22+#include <QUrl>
23+
24+class DeclarativePlaylist : public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(QUrl currentSource READ currentSource NOTIFY currentSourceChanged)
28+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
29+
30+public:
31+ DeclarativePlaylist(QObject *parent = 0);
32+
33+ QUrl currentSource() const;
34+ int currentIndex() const;
35+ void setCurrentIndex(int currentIndex);
36+
37+public Q_SLOTS:
38+ QUrl source(int index) const;
39+ bool addSource(const QUrl &source);
40+ bool clear();
41+
42+Q_SIGNALS:
43+ void currentSourceChanged();
44+ void currentIndexChanged();
45+
46+private:
47+ Q_DISABLE_COPY(DeclarativePlaylist)
48+
49+ int m_currentIndex;
50+ QList<QUrl> m_medias;
51+};
52+
53+#endif
tests/mocks/QtMultimedia/plugin.cpp+2
@@ -18,6 +18,7 @@
1818
1919#include "plugin.h"
2020#include "audio.h"
21+#include "declarativeplaylist.h"
2122
2223#include <QtQml/qqml.h>
2324
@@ -25,4 +26,5 @@ void MockQtMultimediaPlugin::registerTypes(const char *uri)
2526{
2627 Q_ASSERT(uri == QLatin1String("QtMultimedia"));
2728 qmlRegisterType<Audio>(uri, 5, 0, "Audio");
29+ qmlRegisterType<DeclarativePlaylist>(uri, 5, 0, "Playlist");
2830}
tests/plugins/Dash/cardcreator/8.res+97−97
@@ -1,112 +1,112 @@
1−AbstractButton {
2− id: root;
3− property var template;
4− property var components;
5− property var cardData;
6− property var artShapeBorderSource: undefined;
7− property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int titleAlignment: Text.AlignLeft;
10− property int fixedHeaderHeight: -1;
11− property size fixedArtShapeSize: Qt.size(-1, -1);
12− readonly property string title: cardData && cardData["title"] || "";
13− property bool asynchronous: true;
14− property bool showHeader: true;
15− implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
17−
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property var artShapeBorderSource: undefined;
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property int titleAlignment: Text.AlignLeft;
9+ property int fixedHeaderHeight: -1;
10+ property size fixedArtShapeSize: Qt.size(-1, -1);
11+ readonly property string title: cardData && cardData["title"] || "";
12+ property bool asynchronous: true;
13+ property bool showHeader: true;
14+ implicitWidth: childrenRect.width;
15+ enabled: true;
16+
1817readonly property size artShapeSize: Qt.size(-1, -1);
1918readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
2019Label {
21− id: titleLabel;
22− objectName: "titleLabel";
23− anchors { right: parent.right;
20+ id: titleLabel;
21+ objectName: "titleLabel";
22+ anchors { right: parent.right;
2423rightMargin: units.gu(1);
2524left: audioButton.right;
2625 leftMargin: units.gu(1);
2726top: parent.top;
2827 topMargin: units.gu(1);
2928 }
30− elide: Text.ElideRight;
31− fontSize: "small";
32− wrapMode: Text.Wrap;
33− maximumLineCount: 2;
34− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
35− color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
36− visible: showHeader ;
37− text: root.title;
38− font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
39− horizontalAlignment: root.titleAlignment;
29+ elide: Text.ElideRight;
30+ fontSize: "small";
31+ wrapMode: Text.Wrap;
32+ maximumLineCount: 2;
33+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
34+ color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
35+ visible: showHeader ;
36+ text: root.title;
37+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
38+ horizontalAlignment: root.titleAlignment;
4039 }
41−Label {
42− id: subtitleLabel;
43− objectName: "subtitleLabel";
44− anchors { left: titleLabel.left;
45− leftMargin: titleLabel.leftMargin;
46−rightMargin: units.gu(1);
47−right: titleLabel.right;
40+Label {
41+ id: subtitleLabel;
42+ objectName: "subtitleLabel";
43+ anchors { left: titleLabel.left;
44+ leftMargin: titleLabel.leftMargin;
45+rightMargin: units.gu(1);
46+right: titleLabel.right;
4847top: titleLabel.bottom;
49− }
50− anchors.topMargin: units.dp(2);
51− elide: Text.ElideRight;
52− maximumLineCount: 1;
53− fontSize: "x-small";
54− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
55− color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
56− visible: titleLabel.visible && titleLabel.text;
57− text: cardData && cardData["subtitle"] || "";
58− font.weight: Font.Light;
48+ }
49+ anchors.topMargin: units.dp(2);
50+ elide: Text.ElideRight;
51+ maximumLineCount: 1;
52+ fontSize: "x-small";
53+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
54+ color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
55+ visible: titleLabel.visible && titleLabel.text;
56+ text: cardData && cardData["subtitle"] || "";
57+ font.weight: Font.Light;
5958 }
60−CardAudioProgress {
61− id: audioProgressBar;
62− duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0;
63− source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
64− anchors {
65− bottom: audioButton.bottom;
66− left: audioButton.right;
67− right: parent.right;
68− margins: units.gu(1);
69− }
70− color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
71− }AbstractButton {
72− id: audioButton;
73− anchors.fill: undefined;
74− width: height;
75− height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
76− readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
77− UbuntuShape {
78− anchors.fill: parent;
79− visible: parent.pressed;
80− radius: "medium";
81− borderSource: "radius_pressed.sci"
82− }
83− Icon {
84− anchors.fill: parent;
85− anchors.margins: parent.height > units.gu(5) ? units.gu(2) : units.gu(0);
86− opacity: 0.9;
87− name: DashAudioPlayer.isCurrentSource(parent.source) && DashAudioPlayer.playing ? "media-playback-pause" : "media-playback-start";
88− }
89− onClicked: {
90− if (DashAudioPlayer.isCurrentSource(source)) {
91− if (DashAudioPlayer.playing) {
92− DashAudioPlayer.pause();
93− } else {
94− DashAudioPlayer.play();
95− }
96− } else {
97− DashAudioPlayer.playSource(source);
98− }
99− }
100− onPressAndHold: {
101− root.pressAndHold();
102− }
103− }UbuntuShape {
104− id: touchdown;
105− objectName: "touchdown";
106− anchors { fill: root }
107− visible: root.pressed;
108− radius: "medium";
109− borderSource: "radius_pressed.sci"
59+CardAudioProgress {
60+ id: audioProgressBar;
61+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0;
62+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
63+ anchors {
64+ bottom: audioButton.bottom;
65+ left: audioButton.right;
66+ right: parent.right;
67+ margins: units.gu(1);
68+ }
69+ color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
70+ }AbstractButton {
71+ id: audioButton;
72+ anchors.fill: undefined;
73+ width: height;
74+ height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
75+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
76+ readonly property url playlist: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null;
77+ UbuntuShape {
78+ anchors.fill: parent;
79+ visible: parent.pressed;
80+ radius: "medium";
81+ borderSource: "radius_pressed.sci"
82+ }
83+ Icon {
84+ anchors.fill: parent;
85+ anchors.margins: parent.height > units.gu(5) ? units.gu(2) : units.gu(0);
86+ opacity: 0.9;
87+ name: DashAudioPlayer.isCurrentSource(parent.source) && DashAudioPlayer.playing ? "media-playback-pause" : "media-playback-start";
88+ }
89+ onClicked: {
90+ if (DashAudioPlayer.isCurrentSource(source)) {
91+ if (DashAudioPlayer.playing) {
92+ DashAudioPlayer.pause();
93+ } else {
94+ DashAudioPlayer.play();
95+ }
96+ } else {
97+ DashAudioPlayer.playSource(source, playlist);
98+ }
99+ }
100+ onPressAndHold: {
101+ root.pressAndHold();
102+ }
103+ }UbuntuShape {
104+ id: touchdown;
105+ objectName: "touchdown";
106+ anchors { fill: root }
107+ visible: root.pressed;
108+ radius: "medium";
109+ borderSource: "radius_pressed.sci"
110110 }
111111implicitHeight: audioButton.height;
112112}