Merge lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app

Proposed by Andrew Hayzen
Status: Merged
Approved by: Victor Thompson
Approved revision: 943
Merged at revision: 964
Proposed branch: lp:~music-app-dev/music-app/media-hub-bg-playlists-rework
Merge into: lp:music-app
Diff against target: 2709 lines (+461/-752)
30 files modified
app/components/BlurredBackground.qml (+2/-2)
app/components/Flickables/MultiSelectListView.qml (+9/-2)
app/components/HeadState/MultiSelectHeadState.qml (+4/-4)
app/components/Helpers/ContentHubHelper.qml (+11/-8)
app/components/Helpers/UriHandlerHelper.qml (+17/-32)
app/components/Helpers/UserMetricsHelper.qml (+10/-7)
app/components/ListItemActions/AddToPlaylist.qml (+6/-2)
app/components/ListItemActions/AddToQueue.qml (+2/-2)
app/components/MusicToolbar.qml (+37/-23)
app/components/NowPlayingFullView.qml (+57/-17)
app/components/NowPlayingToolbar.qml (+18/-30)
app/components/Player.qml (+0/-240)
app/components/Queue.qml (+18/-20)
app/components/ViewButton/PlayAllButton.qml (+6/-2)
app/components/ViewButton/QueueAllButton.qml (+6/-2)
app/components/ViewButton/ShuffleButton.qml (+6/-2)
app/logic/meta-database.js (+14/-4)
app/logic/playlists.js (+9/-2)
app/music-app.qml (+93/-220)
app/ui/AddToPlaylist.qml (+0/-1)
app/ui/ContentHubExport.qml (+8/-17)
app/ui/NowPlaying.qml (+29/-16)
app/ui/Playlists.qml (+0/-1)
app/ui/Recent.qml (+0/-1)
app/ui/Songs.qml (+2/-3)
debian/changelog (+4/-1)
debian/control (+1/-0)
manifest.json.in (+1/-1)
tests/autopilot/music_app/__init__.py (+30/-11)
tests/autopilot/music_app/tests/test_music.py (+61/-79)
To merge this branch: bzr merge lp:~music-app-dev/music-app/media-hub-bg-playlists-rework
Reviewer Review Type Date Requested Status
Jenkins Bot continuous-integration Approve
Victor Thompson Approve
Review via email: mp+275912@code.launchpad.net

Commit message

* Add support for media-hub background playlists
* Bump framework to 15.04.3-qml
* Bump QtMultimedia import to 5.6

Description of the change

* Add support for media-hub background playlists
* Bump framework to 15.04.3-qml
* Bump QtMultimedia import to 5.6

To post a comment you must log in.
Revision history for this message
Victor Thompson (vthompson) wrote :

I added a few inline comments. There are many TODOs and FIXMEs that should still be investigated/fixed. If we end up leaving any in the merge into trunk, we should maybe clearly tag them with something like "bgplaylists" so we can easily find them in the near future.

review: Needs Fixing
Revision history for this message
Victor Thompson (vthompson) wrote :

Another small notable thing to mention is that the applicable debian/control packages should be updated such that the prereq media-hub packages are installed.

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

I got autopilot passing \o/

autopilot PASS

Revision history for this message
Victor Thompson (vthompson) wrote :

This is looking pretty good. I have some inline comments. The only other minor thing is to update the copyright header where appropriate with 2016.

Also, I mentioned this before: I think we need to put something in the control file to require the new media-hub and other packages be installed.

review: Needs Fixing
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) Is there a reason this length of time was chosen?

48ms is the maximum possible time we can wait without being noticeable to the user (less than 50ms) that is still divisible by 16ms. (note that this worked when set to 16ms on mako to me, I just wanted to give extra time to slower devices)

2) Hasn't this bug been fixed and released?

Nope, using print_tree I could not see the MediaPlayer or the property var :-/ (Would be nice if it was though :-) )

3) Consider changing to a switch statement. Since there are only 3 conditions it isn't too bad though.

I'm not sure that can be a switch statement? What would be the control ? if you did switch(settings.shuffle) you couldn't have a case for repeat? I think it reads fine as an if, else if, else

4) Could you change "bgplaylists" to something more specific? MediaPlayer's playlist/queue or something?

Fixed.

I also noticed that the positionAt currentIndex was broken when switching to the queue view, currently investigating could be due to the Layouts change as then the height isn't 'fixed'.

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

OK, fixed the positionAt issue, if we land trunk into the store before bgplaylists we should include this patch [0]

0 - http://bazaar.launchpad.net/~music-app-dev/music-app/media-hub-bg-playlists-rework/revision/931

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
933. By Andrew Hayzen

* Fix for ContentHubExport.qml missing migration to listitem layout
* Fix for ContentHubHelper.qml referencing queue worker and not pushing now playing page

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Recent changes look good and fix the Export song issue.

Do we need to add anything to the debian/control file for the in interface? Also, please update the copyright years where applicable.

review: Needs Fixing
934. By Andrew Hayzen

* Add qml-module-qtmultimedia (>=5.5.1-1ubuntu2) to control

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
935. By Andrew Hayzen

* Update copyright dates

Revision history for this message
Victor Thompson (vthompson) wrote :

Clicking "shuffle" or "play all" on an empty playlist brings the user to the Now Playing page. It shouldn't do so and didn't do so before.

review: Needs Fixing
936. By Andrew Hayzen

* Fix Play All/Shuffle All/Queue All so that they only perform their action when the model.count > 0

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Both the "Shuffle" and "Queue all" buttons now do not work for a playlist.

review: Needs Fixing
937. By Andrew Hayzen

* Fix for Shuffle All and Queue All not working with playlists due to them using LibraryListModel

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

URL handler actions worked as they did previously.

One thing I noticed is that we do not go to the Now Playing page when just a track is played, whereas when an album is played we do go to the Now Playing page. The current app does this too--did we consciously do this?

review: Needs Information
Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
938. By Andrew Hayzen

* Fix for toolbar progress hint breaking

939. By Andrew Hayzen

* Ensure that now playing page is shown when a single track is played via uri-handler

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

This looks good now. We should figure out the Jenkins failure before landing, however.

review: Needs Fixing
940. By Andrew Hayzen

* Wait for dialog to be visible in autopilot

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

I have reported bug 1536361 for the keyboard issue.

941. By Andrew Hayzen

* Fix for usermetrics

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Did something change in rc-proposed for this to break? Currently, loading the app shows a blank header and no data. I see the following in the log:

file:///opt/click.ubuntu.com/com.ubuntu.music/2.3.939/app/logic/meta-database.js:162:
 TypeError: Cannot call method 'indexOf' of null^M
file:///opt/click.ubuntu.com/com.ubuntu.music/2.3.939/app/music-app.qml:579: TypeErro
r: Cannot read property 'showToolbar' of null^M

Screenshot: http://i.imgur.com/VyHbi1E.png

review: Needs Information
Revision history for this message
Victor Thompson (vthompson) wrote :

I resolved my issue by clearing some of the stored data for the app. I'm not sure if this was due to other MP's being tested or not. I did some testing to make sure the issue isn't something caused by the upgrade path. Before we merge this, I suggest we do some additional upgrade testing.

942. By Andrew Hayzen

* Protect against null filenames when retrieving the queue

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
943. By Andrew Hayzen

* Remove play + pause in test_next_previous that are now redundant due to how bgplaylists works

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Thanks for your patience with this MP. The code looks good, hands-on testing looks good, and with the only remaining issue being the keyboard issues with AP, I say let's land this. Thanks!

review: Approve
Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/components/BlurredBackground.qml'
--- app/components/BlurredBackground.qml 2015-08-12 23:36:44 +0000
+++ app/components/BlurredBackground.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -25,7 +25,7 @@
25Item {25Item {
26 width: parent.width26 width: parent.width
2727
28 property string art // : player.currentMetaFile === "" ? Qt.resolvedUrl("../graphics/music-app-cover@30.png") : player.currentMetaArt28 property string art
2929
30 // dark layer30 // dark layer
31 Rectangle {31 Rectangle {
3232
=== modified file 'app/components/Flickables/MultiSelectListView.qml'
--- app/components/Flickables/MultiSelectListView.qml 2015-10-18 18:16:05 +0000
+++ app/components/Flickables/MultiSelectListView.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -25,6 +25,10 @@
25 // so we need to expose if in multiselect mode for the header states25 // so we need to expose if in multiselect mode for the header states
26 state: ViewItems.selectMode ? "multiselectable" : "normal"26 state: ViewItems.selectMode ? "multiselectable" : "normal"
2727
28 // Describes if model.move() should be called when a list item drag is completed
29 // this is not required on the Queue as onReorder performs playlist.moveItem()
30 property bool autoModelMove: true
31
28 signal clearSelection()32 signal clearSelection()
29 signal closeSelection()33 signal closeSelection()
30 signal reorder(int from, int to)34 signal reorder(int from, int to)
@@ -63,7 +67,10 @@
63 if (event.status == ListItemDrag.Moving) {67 if (event.status == ListItemDrag.Moving) {
64 event.accept = false68 event.accept = false
65 } else if (event.status == ListItemDrag.Dropped) {69 } else if (event.status == ListItemDrag.Dropped) {
66 model.move(event.from, event.to, 1);70 if (autoModelMove) { // check the model should auto move
71 model.move(event.from, event.to, 1);
72 }
73
67 reorder(event.from, event.to)74 reorder(event.from, event.to)
68 }75 }
69 }76 }
7077
=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
--- app/components/HeadState/MultiSelectHeadState.qml 2015-10-18 18:16:05 +0000
+++ app/components/HeadState/MultiSelectHeadState.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 20152 * Copyright (C) 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Victor Thompson <victor.thompson@gmail.com>4 * Victor Thompson <victor.thompson@gmail.com>
5 *5 *
@@ -59,14 +59,14 @@
59 visible: addToQueue59 visible: addToQueue
6060
61 onTriggered: {61 onTriggered: {
62 var items = []62 var items = [];
63 var indicies = listview.getSelectedIndices();63 var indicies = listview.getSelectedIndices();
6464
65 for (var i=0; i < indicies.length; i++) {65 for (var i=0; i < indicies.length; i++) {
66 items.push(listview.model.get(indicies[i], listview.model.RoleModelData));66 items.push(Qt.resolvedUrl(listview.model.get(indicies[i], listview.model.RoleModelData).filename));
67 }67 }
6868
69 trackQueue.appendList(items)69 player.mediaPlayer.playlist.addItems(items);
7070
71 listview.closeSelection()71 listview.closeSelection()
72 }72 }
7373
=== modified file 'app/components/Helpers/ContentHubHelper.qml'
--- app/components/Helpers/ContentHubHelper.qml 2015-10-23 03:08:43 +0000
+++ app/components/Helpers/ContentHubHelper.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2014, 20152 * Copyright (C) 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -154,9 +154,6 @@
154 contentHubWaitForFile.searchPaths = contentHub.searchPaths;154 contentHubWaitForFile.searchPaths = contentHub.searchPaths;
155 contentHubWaitForFile.processId = processId;155 contentHubWaitForFile.processId = processId;
156 contentHubWaitForFile.start();156 contentHubWaitForFile.start();
157
158 // Stop queue loading in bg
159 queueLoaderWorker.canLoad = false
160 } else {157 } else {
161 contentHubWaitForFile.searchPaths.push.apply(contentHubWaitForFile.searchPaths, contentHub.searchPaths);158 contentHubWaitForFile.searchPaths.push.apply(contentHubWaitForFile.searchPaths, contentHub.searchPaths);
162 contentHubWaitForFile.count = 0;159 contentHubWaitForFile.count = 0;
@@ -220,15 +217,21 @@
220 else {217 else {
221 stopTimer();218 stopTimer();
222219
223 trackQueue.clear();220 player.mediaPlayer.playlist.clearWrapper();
221
222 var items = [];
224223
225 for (i=0; i < searchPaths.length; i++) {224 for (i=0; i < searchPaths.length; i++) {
226 model = musicStore.lookup(decodeFileURI(searchPaths[i]))225 // Don't need to check if in ms2 as that is done above
227226 items.push(Qt.resolvedUrl(decodeURIComponent(searchPaths[i])))
228 trackQueue.append(makeDict(model));
229 }227 }
230228
229 player.mediaPlayer.playlist.addItems(items);
230
231 trackQueueClick(0);231 trackQueueClick(0);
232
233 // Show the Now playing page and make sure the track is visible
234 tabs.pushNowPlaying();
232 }235 }
233 }236 }
234 }237 }
235238
=== modified file 'app/components/Helpers/UriHandlerHelper.qml'
--- app/components/Helpers/UriHandlerHelper.qml 2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UriHandlerHelper.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -38,14 +38,6 @@
38 }38 }
3939
40 function processAlbum(uri) {40 function processAlbum(uri) {
41 // Stop queue loading in the background
42 queueLoaderWorker.canLoad = false
43
44 if (queueLoaderWorker.processing > 0) {
45 waitForWorker.workerStop(queueLoaderWorker, processAlbum, [uri])
46 return;
47 }
48
49 selectedAlbum = true;41 selectedAlbum = true;
50 var split = uri.split("/");42 var split = uri.split("/");
5143
@@ -60,34 +52,27 @@
60 }52 }
6153
62 function processFile(uri, play) {54 function processFile(uri, play) {
63 // Stop queue loading in the background
64 queueLoaderWorker.canLoad = false
65
66 if (queueLoaderWorker.processing > 0) {
67 waitForWorker.workerStop(queueLoaderWorker, processFile, [uri, play])
68 return;
69 }
70
71 // Lookup track in songs model55 // Lookup track in songs model
72 var track = musicStore.lookup(decodeFileURI(uri));56 var track = musicStore.lookup(decodeFileURI(uri));
7357
74 if (!track) {58 if (!track) {
75 console.debug("Unknown file " + uri + ", skipping")59 console.debug("Unknown file " + uri + ", skipping")
76 return;60 } else {
77 }61 if (play) {
7862 // clear play queue
79 if (play) {63 player.mediaPlayer.playlist.clearWrapper()
80 // clear play queue64 }
81 trackQueue.clear()65
82 }66 // enqueue
8367 player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(track.filename));
84 // enqueue68
85 trackQueue.append(makeDict(track));69 // play first URI
8670 if (play) {
87 // play first URI71 trackQueueClick(player.mediaPlayer.playlist.itemCount - 1);
88 if (play) {72 tabs.pushNowPlaying(); // ensure now playing is shown for first
89 trackQueueClick(trackQueue.model.count - 1);73 }
90 }74 }
75
91 }76 }
9277
93 function process(uri, play) {78 function process(uri, play) {
9479
=== modified file 'app/components/Helpers/UserMetricsHelper.qml'
--- app/components/Helpers/UserMetricsHelper.qml 2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UserMetricsHelper.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -36,21 +36,24 @@
36 // Connections for usermetrics36 // Connections for usermetrics
37 Connections {37 Connections {
38 id: userMetricPlayerConnection38 id: userMetricPlayerConnection
39 target: player39 target: player.mediaPlayer
40
40 property bool songCounted: false41 property bool songCounted: false
4142
42 onSourceChanged: {
43 songCounted = false
44 }
45
46 onPositionChanged: {43 onPositionChanged: {
47 // Increment song count on Welcome screen if song has been44 // Increment song count on Welcome screen if song has been
48 // playing for over 10 seconds.45 // playing for over 10 seconds.
49 if (player.position > 10000 && !songCounted) {46 if (player.mediaPlayer.position > 10000 && !songCounted) {
50 songCounted = true47 songCounted = true
51 songsMetric.increment()48 songsMetric.increment()
52 console.debug("Increment UserMetrics")49 console.debug("Increment UserMetrics")
53 }50 }
54 }51 }
55 }52 }
53
54 Connections {
55 target: player.mediaPlayer.playlist
56 onCurrentIndexChanged: userMetricPlayerConnection.songCounted = false
57 onCurrentItemSourceChanged: userMetricPlayerConnection.songCounted = false
58 }
56}59}
5760
=== modified file 'app/components/ListItemActions/AddToPlaylist.qml'
--- app/components/ListItemActions/AddToPlaylist.qml 2015-10-18 18:16:05 +0000
+++ app/components/ListItemActions/AddToPlaylist.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2014, 20152 * Copyright (C) 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -25,10 +25,14 @@
25 objectName: "addToPlaylistAction"25 objectName: "addToPlaylistAction"
26 text: i18n.tr("Add to playlist")26 text: i18n.tr("Add to playlist")
2727
28 // Used when model can't be given to add to playlist
29 // for example in the Queue it is called metaModel not model
30 property var modelOverride: null
31
28 onTriggered: {32 onTriggered: {
29 console.debug("Debug: Add track to playlist");33 console.debug("Debug: Add track to playlist");
3034
31 mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),35 mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
32 {"chosenElements": [makeDict(model)]})36 {"chosenElements": [modelOverride || makeDict(model)]})
33 }37 }
34}38}
3539
=== modified file 'app/components/ListItemActions/AddToQueue.qml'
--- app/components/ListItemActions/AddToQueue.qml 2015-08-12 23:36:44 +0000
+++ app/components/ListItemActions/AddToQueue.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2014, 20152 * Copyright (C) 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -29,6 +29,6 @@
2929
30 onTriggered: {30 onTriggered: {
31 console.debug("Debug: Add track to queue: " + model)31 console.debug("Debug: Add track to queue: " + model)
32 trackQueue.append(model)32 player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(model.filename))
33 }33 }
34}34}
3535
=== modified file 'app/components/MusicToolbar.qml'
--- app/components/MusicToolbar.qml 2015-08-12 23:36:44 +0000
+++ app/components/MusicToolbar.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -18,7 +18,7 @@
18 */18 */
1919
20import QtQuick 2.420import QtQuick 2.4
21import QtMultimedia 5.021import QtMultimedia 5.6
22import Ubuntu.Components 1.322import Ubuntu.Components 1.3
2323
24Rectangle {24Rectangle {
@@ -42,7 +42,7 @@
42 anchors {42 anchors {
43 fill: parent43 fill: parent
44 }44 }
45 state: trackQueue.model.count === 0 ? "disabled" : "enabled"45 state: player.mediaPlayer.playlist.empty ? "disabled" : "enabled"
46 states: [46 states: [
47 State {47 State {
48 name: "disabled"48 name: "disabled"
@@ -104,7 +104,7 @@
104 }104 }
105 color: "#FFF"105 color: "#FFF"
106 height: units.gu(4)106 height: units.gu(4)
107 name: player.playbackState === MediaPlayer.PlayingState ?107 name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
108 "media-playback-pause" : "media-playback-start"108 "media-playback-pause" : "media-playback-start"
109 objectName: "disabledSmallPlayShape"109 objectName: "disabledSmallPlayShape"
110 width: height110 width: height
@@ -116,11 +116,10 @@
116 fill: parent116 fill: parent
117 }117 }
118 onClicked: {118 onClicked: {
119 if (trackQueue.model.count === 0) {119 if (player.mediaPlayer.playlist.empty) {
120 playRandomSong();120 playRandomSong();
121 }121 } else {
122 else {122 player.mediaPlayer.toggle();
123 player.toggle();
124 }123 }
125 }124 }
126 }125 }
@@ -144,7 +143,7 @@
144 left: parent.left143 left: parent.left
145 top: parent.top144 top: parent.top
146 }145 }
147 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]146 covers: [player.currentMeta]
148 size: parent.height147 size: parent.height
149 }148 }
150149
@@ -170,8 +169,9 @@
170 elide: Text.ElideRight169 elide: Text.ElideRight
171 fontSize: "small"170 fontSize: "small"
172 font.weight: Font.DemiBold171 font.weight: Font.DemiBold
173 text: player.currentMetaTitle === ""172 text: player.currentMeta.title === ""
174 ? player.source : player.currentMetaTitle173 ? player.mediaPlayer.playlist.currentItemSource
174 : player.currentMeta.title
175 }175 }
176176
177 /* Artist of track */177 /* Artist of track */
@@ -185,7 +185,7 @@
185 elide: Text.ElideRight185 elide: Text.ElideRight
186 fontSize: "small"186 fontSize: "small"
187 opacity: 0.4187 opacity: 0.4
188 text: player.currentMetaArtist188 text: player.currentMeta.author
189 }189 }
190 }190 }
191191
@@ -197,9 +197,9 @@
197 rightMargin: units.gu(3)197 rightMargin: units.gu(3)
198 verticalCenter: parent.verticalCenter198 verticalCenter: parent.verticalCenter
199 }199 }
200 color: "#FFF"200 color: playerControlsPlayButtonMouseArea.pressed ? UbuntuColors.blue : "white"
201 height: units.gu(4)201 height: units.gu(4)
202 name: player.playbackState === MediaPlayer.PlayingState ?202 name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
203 "media-playback-pause" : "media-playback-start"203 "media-playback-pause" : "media-playback-start"
204 objectName: "playShape"204 objectName: "playShape"
205 width: height205 width: height
@@ -217,12 +217,13 @@
217217
218 /* Mouse area for the play button (ontop of the jump to now playing) */218 /* Mouse area for the play button (ontop of the jump to now playing) */
219 MouseArea {219 MouseArea {
220 id: playerControlsPlayButtonMouseArea
220 anchors {221 anchors {
221 bottom: parent.bottom222 bottom: parent.bottom
222 horizontalCenter: playerControlsPlayButton.horizontalCenter223 horizontalCenter: playerControlsPlayButton.horizontalCenter
223 top: parent.top224 top: parent.top
224 }225 }
225 onClicked: player.toggle()226 onClicked: player.mediaPlayer.toggle()
226 width: units.gu(8)227 width: units.gu(8)
227228
228 Rectangle {229 Rectangle {
@@ -260,16 +261,29 @@
260 }261 }
261 color: UbuntuColors.blue262 color: UbuntuColors.blue
262 height: parent.height263 height: parent.height
263 width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0264 width: player.mediaPlayer.progress * playerControlsProgressBar.width
265
266 // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
267 // emit until it changes to the PLAYING state. But by asking for a
268 // value we get gst to perform a query and return a result
269 // However this has to be done once the source is set, hence the delay
270 //
271 // NOTE: This does not solve when the currentIndex is removed though
272 Timer {
273 id: refreshProgressTimer
274 interval: 48
275 repeat: false
276
277 // Use binding so the width updates and value isn't saved
278 onTriggered: playerControlsProgressBarHint.width = Qt.binding(function() { return player.mediaPlayer.progress * playerControlsProgressBar.width; })
279 }
264280
265 Connections {281 Connections {
266 target: player282 target: player.mediaPlayer.playlist
267 onPositionChanged: {283 // Call timer when source or index changes
268 playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width284 // so we call even if there are duplicate sources or source removal
269 }285 onCurrentItemSourceChanged: refreshProgressTimer.start()
270 onStopped: {286 onCurrentIndexChanged: refreshProgressTimer.start()
271 playerControlsProgressBarHint.width = 0;
272 }
273 }287 }
274 }288 }
275 }289 }
276290
=== modified file 'app/components/NowPlayingFullView.qml'
--- app/components/NowPlayingFullView.qml 2015-10-18 17:45:48 +0000
+++ app/components/NowPlayingFullView.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -51,7 +51,7 @@
51 CoverGrid {51 CoverGrid {
52 id: albumImage52 id: albumImage
53 anchors.centerIn: parent53 anchors.centerIn: parent
54 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]54 covers: [player.currentMeta]
55 size: parent.height55 size: parent.height
56 }56 }
57 }57 }
@@ -92,7 +92,15 @@
92 fontSize: "x-large"92 fontSize: "x-large"
93 maximumLineCount: 293 maximumLineCount: 2
94 objectName: "playercontroltitle"94 objectName: "playercontroltitle"
95 text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle95 text: {
96 if (player.mediaPlayer.playlist.empty) {
97 ""
98 } else if (player.currentMeta.title === "") {
99 player.mediaPlayer.playlist.currentSource
100 } else {
101 player.currentMeta.title
102 }
103 }
96 wrapMode: Text.WordWrap104 wrapMode: Text.WordWrap
97 }105 }
98106
@@ -108,7 +116,7 @@
108 color: styleMusic.nowPlaying.labelSecondaryColor116 color: styleMusic.nowPlaying.labelSecondaryColor
109 elide: Text.ElideRight117 elide: Text.ElideRight
110 fontSize: "small"118 fontSize: "small"
111 text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist119 text: player.mediaPlayer.playlist.empty ? "" : player.currentMeta.author
112 }120 }
113 }121 }
114122
@@ -122,12 +130,13 @@
122130
123 onReleased: {131 onReleased: {
124 var diff = mouse.x - lastX132 var diff = mouse.x - lastX
133
125 if (Math.abs(diff) < units.gu(4)) {134 if (Math.abs(diff) < units.gu(4)) {
126 return;135 return;
127 } else if (diff < 0) {136 } else if (diff < 0) {
128 player.nextSong()137 player.mediaPlayer.playlist.nextWrapper()
129 } else if (diff > 0) {138 } else if (diff > 0) {
130 player.previousSong()139 player.mediaPlayer.playlist.previousWrapper()
131 }140 }
132 }141 }
133 }142 }
@@ -167,7 +176,7 @@
167 fontSize: "small"176 fontSize: "small"
168 height: parent.height177 height: parent.height
169 horizontalAlignment: Text.AlignHCenter178 horizontalAlignment: Text.AlignHCenter
170 text: durationToString(player.position)179 text: durationToString(player.mediaPlayer.position)
171 verticalAlignment: Text.AlignVCenter180 verticalAlignment: Text.AlignVCenter
172 width: units.gu(3)181 width: units.gu(3)
173 }182 }
@@ -176,10 +185,10 @@
176 id: progressSliderMusic185 id: progressSliderMusic
177 anchors.left: parent.left186 anchors.left: parent.left
178 anchors.right: parent.right187 anchors.right: parent.right
179 maximumValue: player.duration // load value at startup188 maximumValue: player.mediaPlayer.duration || 1 // fallback to 1 when 0 so that the progress bar works
180 objectName: "progressSliderShape"189 objectName: "progressSliderShape"
181 style: UbuntuBlueSliderStyle {}190 style: UbuntuBlueSliderStyle {}
182 value: player.position // load value at startup191 value: player.mediaPlayer.position // load value at startup
183192
184 function formatValue(v) {193 function formatValue(v) {
185 if (seeking) { // update position label while dragging194 if (seeking) { // update position label while dragging
@@ -194,7 +203,7 @@
194203
195 onSeekingChanged: {204 onSeekingChanged: {
196 if (seeking === false) {205 if (seeking === false) {
197 musicToolbarFullPositionLabel.text = durationToString(player.position)206 musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
198 }207 }
199 }208 }
200209
@@ -203,22 +212,23 @@
203212
204 if (!pressed) {213 if (!pressed) {
205 seeked = true214 seeked = true
206 player.seek(value)215 player.mediaPlayer.seek(value)
207216
208 musicToolbarFullPositionLabel.text = durationToString(value)217 musicToolbarFullPositionLabel.text = durationToString(value)
209 }218 }
210 }219 }
211220
212 Connections {221 Connections {
213 target: player222 target: player.mediaPlayer
214 onPositionChanged: {223 onPositionChanged: {
215 // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)224 // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
216 if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {225 if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
217 musicToolbarFullPositionLabel.text = durationToString(player.position)226 musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
218 musicToolbarFullDurationLabel.text = durationToString(player.duration)227 musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration)
219228
220 progressSliderMusic.value = player.position229 progressSliderMusic.value = player.mediaPlayer.position
221 progressSliderMusic.maximumValue = player.duration230 // fallback to 1 when 0 so that the progress bar works
231 progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
222 }232 }
223233
224 progressSliderMusic.seeked = false;234 progressSliderMusic.seeked = false;
@@ -240,9 +250,39 @@
240 fontSize: "small"250 fontSize: "small"
241 height: parent.height251 height: parent.height
242 horizontalAlignment: Text.AlignHCenter252 horizontalAlignment: Text.AlignHCenter
243 text: durationToString(player.duration)253 text: durationToString(player.mediaPlayer.duration || 1)
244 verticalAlignment: Text.AlignVCenter254 verticalAlignment: Text.AlignVCenter
245 width: units.gu(3)255 width: units.gu(3)
246 }256 }
257
258 // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
259 // emit until it changes to the PLAYING state. But by asking for a
260 // value we get gst to perform a query and return a result
261 // However this has to be done once the source is set, hence the delay
262 //
263 // NOTE: This does not solve when the currentIndex is removed though
264 Timer {
265 id: refreshProgressTimer
266 interval: 48
267 repeat: false
268 onTriggered: {
269 if (!progressSliderMusic.seeking) {
270 musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position);
271 musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration || 1);
272
273 progressSliderMusic.value = player.mediaPlayer.position
274 // fallback to 1 when 0 so that the progress bar works
275 progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
276 }
277 }
278 }
279
280 Connections {
281 target: player.mediaPlayer.playlist
282 // Call timer when source or index changes
283 // so we call even if there are duplicate sources or source removal
284 onCurrentItemSourceChanged: refreshProgressTimer.start()
285 onCurrentIndexChanged: refreshProgressTimer.start()
286 }
247 }287 }
248}288}
249289
=== modified file 'app/components/NowPlayingToolbar.qml'
--- app/components/NowPlayingToolbar.qml 2015-08-12 23:36:44 +0000
+++ app/components/NowPlayingToolbar.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -17,7 +17,7 @@
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */18 */
1919
20import QtMultimedia 5.020import QtMultimedia 5.6
21import QtQuick 2.421import QtQuick 2.4
22import Ubuntu.Components 1.322import Ubuntu.Components 1.3
2323
@@ -37,7 +37,6 @@
37 anchors.rightMargin: units.gu(1)37 anchors.rightMargin: units.gu(1)
38 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter38 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
39 height: units.gu(6)39 height: units.gu(6)
40 opacity: player.repeat ? 1 : .4
41 width: height40 width: height
42 onClicked: player.repeat = !player.repeat41 onClicked: player.repeat = !player.repeat
4342
@@ -47,23 +46,23 @@
47 width: height46 width: height
48 anchors.verticalCenter: parent.verticalCenter47 anchors.verticalCenter: parent.verticalCenter
49 anchors.horizontalCenter: parent.horizontalCenter48 anchors.horizontalCenter: parent.horizontalCenter
50 color: "white"49 color: parent.pressed ? UbuntuColors.blue : "white"
51 name: "media-playlist-repeat"50 name: "media-playlist-repeat"
52 objectName: "repeatShape"51 objectName: "repeatShape"
53 opacity: player.repeat ? 1 : .452 opacity: player.repeat ? 1 : .2
54 }53 }
55 }54 }
5655
57 /* Previous button */56 /* Previous button */
58 MouseArea {57 MouseArea {
59 id: nowPlayingPreviousButton58 id: nowPlayingPreviousButton
59 enabled: player.mediaPlayer.playlist.canGoPrevious
60 anchors.right: nowPlayingPlayButton.left60 anchors.right: nowPlayingPlayButton.left
61 anchors.rightMargin: units.gu(1)61 anchors.rightMargin: units.gu(1)
62 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter62 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
63 height: units.gu(6)63 height: units.gu(6)
64 opacity: trackQueue.model.count === 0 ? .4 : 1
65 width: height64 width: height
66 onClicked: player.previousSong()65 onClicked: player.mediaPlayer.playlist.previousWrapper()
6766
68 Icon {67 Icon {
69 id: nowPlayingPreviousIndicator68 id: nowPlayingPreviousIndicator
@@ -71,10 +70,10 @@
71 width: height70 width: height
72 anchors.verticalCenter: parent.verticalCenter71 anchors.verticalCenter: parent.verticalCenter
73 anchors.horizontalCenter: parent.horizontalCenter72 anchors.horizontalCenter: parent.horizontalCenter
74 color: "white"73 color: parent.pressed ? UbuntuColors.blue : "white"
75 name: "media-skip-backward"74 name: "media-skip-backward"
76 objectName: "previousShape"75 objectName: "previousShape"
77 opacity: 176 opacity: parent.enabled ? 1 : .2
78 }77 }
79 }78 }
8079
@@ -84,7 +83,7 @@
84 anchors.centerIn: parent83 anchors.centerIn: parent
85 height: units.gu(10)84 height: units.gu(10)
86 width: height85 width: height
87 onClicked: player.toggle()86 onClicked: player.mediaPlayer.toggle()
8887
89 Icon {88 Icon {
90 id: nowPlayingPlayIndicator89 id: nowPlayingPlayIndicator
@@ -92,8 +91,8 @@
92 width: height91 width: height
93 anchors.verticalCenter: parent.verticalCenter92 anchors.verticalCenter: parent.verticalCenter
94 anchors.horizontalCenter: parent.horizontalCenter93 anchors.horizontalCenter: parent.horizontalCenter
95 color: "white"94 color: parent.pressed ? UbuntuColors.blue : "white"
96 name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"95 name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
97 objectName: "playShape"96 objectName: "playShape"
98 }97 }
99 }98 }
@@ -104,10 +103,10 @@
104 anchors.left: nowPlayingPlayButton.right103 anchors.left: nowPlayingPlayButton.right
105 anchors.leftMargin: units.gu(1)104 anchors.leftMargin: units.gu(1)
106 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter105 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
106 enabled: player.mediaPlayer.playlist.canGoNext
107 height: units.gu(6)107 height: units.gu(6)
108 opacity: trackQueue.model.count === 0 ? .4 : 1
109 width: height108 width: height
110 onClicked: player.nextSong()109 onClicked: player.mediaPlayer.playlist.nextWrapper()
111110
112 Icon {111 Icon {
113 id: nowPlayingNextIndicator112 id: nowPlayingNextIndicator
@@ -115,10 +114,10 @@
115 width: height114 width: height
116 anchors.verticalCenter: parent.verticalCenter115 anchors.verticalCenter: parent.verticalCenter
117 anchors.horizontalCenter: parent.horizontalCenter116 anchors.horizontalCenter: parent.horizontalCenter
118 color: "white"117 color: parent.pressed ? UbuntuColors.blue : "white"
119 name: "media-skip-forward"118 name: "media-skip-forward"
120 objectName: "forwardShape"119 objectName: "forwardShape"
121 opacity: 1120 opacity: parent.enabled ? 1 : .2
122 }121 }
123 }122 }
124123
@@ -129,7 +128,6 @@
129 anchors.leftMargin: units.gu(1)128 anchors.leftMargin: units.gu(1)
130 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter129 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
131 height: units.gu(6)130 height: units.gu(6)
132 opacity: player.shuffle ? 1 : .4
133 width: height131 width: height
134 onClicked: player.shuffle = !player.shuffle132 onClicked: player.shuffle = !player.shuffle
135133
@@ -139,10 +137,10 @@
139 width: height137 width: height
140 anchors.verticalCenter: parent.verticalCenter138 anchors.verticalCenter: parent.verticalCenter
141 anchors.horizontalCenter: parent.horizontalCenter139 anchors.horizontalCenter: parent.horizontalCenter
142 color: "white"140 color: parent.pressed ? UbuntuColors.blue : "white"
143 name: "media-playlist-shuffle"141 name: "media-playlist-shuffle"
144 objectName: "shuffleShape"142 objectName: "shuffleShape"
145 opacity: player.shuffle ? 1 : .4143 opacity: player.shuffle ? 1 : .2
146 }144 }
147 }145 }
148146
@@ -166,17 +164,7 @@
166 }164 }
167 color: UbuntuColors.blue165 color: UbuntuColors.blue
168 height: parent.height166 height: parent.height
169 width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0167 width: player.mediaPlayer.progress * playerControlsProgressBar.width
170
171 Connections {
172 target: player
173 onPositionChanged: {
174 playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
175 }
176 onStopped: {
177 playerControlsProgressBarHint.width = 0;
178 }
179 }
180 }168 }
181 }169 }
182}170}
183171
=== added file 'app/components/Player.qml'
--- app/components/Player.qml 1970-01-01 00:00:00 +0000
+++ app/components/Player.qml 2016-01-26 00:01:55 +0000
@@ -0,0 +1,360 @@
1/*
2 * Copyright (C) 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Victor Thompson <victor.thompson@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtMultimedia 5.6
20import QtQuick 2.4
21import Qt.labs.settings 1.0
22
23import QtQuick.LocalStorage 2.0
24import "../logic/meta-database.js" as Library
25
26Item {
27 objectName: "player"
28
29 // For autopilot as we can't access the MediaPlayer object pad.lv/1269578
30 readonly property bool isPlaying: mediaPlayerObject.playbackState === MediaPlayer.PlayingState
31 readonly property alias count: mediaPlayerPlaylist.itemCount
32 readonly property alias currentIndex: mediaPlayerPlaylist.currentIndex
33 readonly property alias currentItemSource: mediaPlayerPlaylist.currentItemSource
34 readonly property alias position: mediaPlayerObject.position
35
36 // FIXME: pad.lv/1269578 use Item as autopilot cannot 'see' a var/QtObject
37 property alias currentMeta: currentMetaItem
38
39 property alias mediaPlayer: mediaPlayerObject
40 property alias repeat: settings.repeat
41 property alias shuffle: settings.shuffle
42
43 Item {
44 id: currentMetaItem
45 objectName: "currentMeta"
46
47 property string album: ""
48 property string art: ""
49 property string author: ""
50 property string filename: ""
51 property string title: ""
52 }
53
54 // Return the metadata for the source given from mediascanner2
55 function metaForSource(source) {
56 var blankMeta = {
57 album: "",
58 art: "",
59 author: "",
60 filename: "",
61 title: ""
62 };
63
64 source = source.toString();
65
66 if (source.indexOf("file://") === 0) {
67 source = source.substring(7);
68 }
69
70 return musicStore.lookup(decodeFileURI(source)) || blankMeta;
71 }
72
73 Settings {
74 id: settings
75 category: "PlayerSettings"
76
77 property bool repeat: true
78 property bool shuffle: false
79 }
80
81 MediaPlayer {
82 id: mediaPlayerObject
83 playlist: Playlist {
84 id: mediaPlayerPlaylist
85 playbackMode: {
86 if (settings.shuffle) {
87 Playlist.Random
88 } else if (settings.repeat) {
89 Playlist.Loop
90 } else {
91 Playlist.Sequential
92 }
93 }
94
95 // as that doesn't emit changes
96 readonly property bool canGoPrevious: { // FIXME: pad.lv/1517580 use previousIndex() > -1 after mh implements it
97 currentIndex !== 0 ||
98 settings.repeat ||
99 settings.shuffle || // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
100 mediaPlayerObject.position > 5000
101 }
102 readonly property bool canGoNext: { // FIXME: pad.lv/1517580 use nextIndex() > -1 after mh implements it
103 currentIndex !== (itemCount - 1) ||
104 settings.repeat ||
105 settings.shuffle // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
106 }
107 readonly property int count: itemCount // header actions etc depend on the model having 'count'
108 readonly property bool empty: itemCount === 0
109 property int pendingCurrentIndex: -1
110 property var pendingCurrentState: null
111 property int pendingShuffle: -1
112
113 onCurrentItemSourceChanged: {
114 var meta = metaForSource(currentItemSource);
115
116 currentMeta.album = meta.album;
117 currentMeta.art = meta.art;
118 currentMeta.author = meta.author;
119 currentMeta.filename = meta.filename;
120 currentMeta.title = meta.title;
121
122 mediaPlayerObject._calcProgress();
123 }
124 onItemChanged: {
125 console.debug("*** Saving play queue in onItemChanged");
126 saveQueue()
127 }
128 onItemInserted: {
129 // When add to queue is done on an empty list currentIndex needs to be set
130 if (start === 0 && currentIndex === -1 && pendingCurrentIndex < 1 && pendingShuffle === -1) {
131 currentIndex = 0;
132
133 pendingCurrentIndex = -1;
134 processPendingCurrentState();
135 }
136
137 // Check if the pendingCurrentIndex is now valid
138 if (pendingCurrentIndex !== -1 && pendingCurrentIndex < itemCount) {
139 currentIndex = pendingCurrentIndex;
140
141 pendingCurrentIndex = -1;
142 processPendingCurrentState();
143 }
144
145 // Check if there is pending shuffle
146 // pendingShuffle holds the expected size of the model
147 if (pendingShuffle > -1 && pendingShuffle <= itemCount) {
148 pendingShuffle = -1;
149
150 nextWrapper(); // find a random track
151 mediaPlayerObject.play(); // next does not enforce play
152 }
153
154 console.debug("*** Saving play queue in onItemInserted");
155 saveQueue()
156 }
157 onItemRemoved: {
158 console.debug("*** Saving play queue in onItemRemoved");
159 saveQueue()
160 }
161
162 function addItemsFromModel(model) {
163 var items = []
164
165 // TODO: remove once playlists uses U1DB
166 if (model.hasOwnProperty("linkLibraryListModel")) {
167 model = model.linkLibraryListModel;
168 }
169
170 for (var i=0; i < model.rowCount; i++) {
171 items.push(Qt.resolvedUrl(model.get(i, model.RoleModelData).filename));
172 }
173
174 addItems(items);
175 }
176
177 // Wrap the clear() method because we need to call stop first
178 function clearWrapper() {
179 // Stop the current playback (this ensures that play is run later)
180 if (mediaPlayerObject.playbackState === MediaPlayer.PlayingState) {
181 mediaPlayerObject.stop();
182 }
183
184 clear();
185 }
186
187 // Replicates a model.get() on a ms2 model
188 function get(index, role) {
189 return metaForSource(itemSource(index));
190 }
191
192 // Wrap the next() method so we can check canGoNext
193 function nextWrapper() {
194 if (canGoNext) {
195 next();
196 }
197 }
198
199 // Wrap the previous() method so we can check canGoPrevious
200 function previousWrapper() {
201 if (canGoPrevious) {
202 previous();
203 }
204 }
205
206 // Process the pending current PlaybackState
207 function processPendingCurrentState() {
208 if (pendingCurrentState === MediaPlayer.PlayingState) {
209 console.debug("Loading pending state play()");
210 mediaPlayerObject.play();
211 } else if (pendingCurrentState === MediaPlayer.PausedState) {
212 console.debug("Loading pending state pause()");
213 mediaPlayerObject.pause();
214 } else if (pendingCurrentState === MediaPlayer.StoppedState) {
215 console.debug("Loading pending state stop()");
216 mediaPlayerObject.stop();
217 }
218
219 pendingCurrentState = null;
220 }
221
222 // Wrapper for removeItems(from, to) so that we can use removeItems(list) until it is implemented upstream
223 function removeItemsWrapper(items) {
224 var previous = -1, end = -1;
225
226 // Sort indexes backwards so we don't have to deal with offsets when removing
227 items.sort(function(a,b) { return b-a; });
228
229 console.debug("To Remove", JSON.stringify(items));
230
231 // Merge ranges of indexes into sets of start, end points
232 // and call removeItems as we go along
233 for (var i=0; i < items.length; i++) {
234 if (end == -1) { // first value found set to first
235 end = items[i];
236 } else if (previous - 1 !== items[i]) { // set has ended (next is not 1 lower)
237 console.debug("RemoveItems", previous, end);
238 player.mediaPlayer.playlist.removeItems(previous, end);
239
240 end = items[i]; // set new high value for the next set
241 }
242
243 previous = items[i]; // last value to check if next is 1 lower
244 }
245
246 // Remove last set in list as well
247 if (items.length > 0) {
248 console.debug("RemoveItems", items[items.length - 1], end);
249 player.mediaPlayer.playlist.removeItems(items[items.length - 1], end);
250 }
251 }
252
253 function saveQueue(start, end) {
254 // FIXME: load and save do not work yet pad.lv/1510225
255 // so use our localstorage method for now
256 // save("/home/phablet/.local/share/com.ubuntu.music/queue.m3u");
257 if (mainView.loadedUI) {
258 // Don't be intelligent, just clear and rebuild the queue for now
259 Library.clearQueue();
260
261 var sources = [];
262
263 for (var i=0; i < mediaPlayerPlaylist.itemCount; i++) {
264 sources.push(mediaPlayerPlaylist.itemSource(i));
265 }
266
267 if (sources.length > 0) {
268 Library.addQueueList(sources);
269 }
270 }
271 }
272
273 function setCurrentIndex(index) {
274 // Set the currentIndex but if the itemCount is too low then wait
275 if (index < mediaPlayerPlaylist.itemCount) {
276 mediaPlayerPlaylist.currentIndex = index;
277 } else {
278 pendingCurrentIndex = index;
279 }
280 }
281
282 function setPendingCurrentState(pendingState) {
283 // Set the PlaybackState to set once pendingCurrentIndex is set
284 pendingCurrentState = pendingState;
285
286 if (pendingCurrentIndex === -1) {
287 processPendingCurrentState();
288 }
289 }
290
291 function setPendingShuffle(modelSize) {
292 // Run next() and play() when the modelSize is reached
293 if (modelSize <= itemCount) {
294 mediaPlayerPlaylist.nextWrapper(); // find a random track
295 mediaPlayerObject.play(); // next does not enforce play
296 } else {
297 pendingShuffle = modelSize;
298 }
299 }
300 }
301
302 property bool endOfMedia: false
303 property double progress: 0
304
305 onDurationChanged: _calcProgress()
306 onPositionChanged: _calcProgress()
307
308 onStatusChanged: {
309 if (status == MediaPlayer.EndOfMedia && !settings.repeat) {
310 console.debug("End of media, stopping.")
311
312 // Tells the onStopped to set the curentIndex = 0
313 endOfMedia = true;
314
315 stop();
316 }
317 }
318
319 onStopped: { // hit when pressing next() on last track with repeat off
320 console.debug("onStopped.")
321
322 // FIXME: Workaround for pad.lv/1494031 in the stopped state
323 // we do not get position/duration info so if there are items in
324 // the queue and we have stopped instead pause
325 if (playlist.itemCount > 0) {
326 // We have just ended media so jump to start of playlist
327 if (endOfMedia) {
328 playlist.currentIndex = 0;
329
330 // Play then pause otherwise when we come from EndOfMedia
331 // if calls next() until EndOfMedia again
332 play();
333 }
334
335 pause();
336 }
337
338 endOfMedia = false; // always reset endOfMedia
339 _calcProgress(); // ensures progress bar has reset
340 }
341
342 function _calcProgress() {
343 if (duration > 0) {
344 progress = position / duration;
345 } else if (position >= duration) {
346 progress = 0;
347 } else {
348 progress = 0;
349 }
350 }
351
352 function toggle() {
353 if (playbackState === MediaPlayer.PlayingState) {
354 pause();
355 } else {
356 play();
357 }
358 }
359 }
360}
0361
=== removed file 'app/components/Player.qml'
--- app/components/Player.qml 2015-06-28 03:06:49 +0000
+++ app/components/Player.qml 1970-01-01 00:00:00 +0000
@@ -1,240 +0,0 @@
1/*
2 * Copyright (C) 2013, 2014, 2015
3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 3.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20import QtQuick 2.4
21import QtMultimedia 5.0
22import QtQuick.LocalStorage 2.0
23import Qt.labs.settings 1.0
24
25/*
26 * This file should *only* manage the media playing and the relevant settings
27 * It should therefore only access MediaPlayer, trackQueue and Settings
28 * Anything else within the app should use Connections to listen for changes
29 */
30
31
32Item {
33 objectName: "player"
34
35 property string currentMetaAlbum: ""
36 property string currentMetaArt: ""
37 property string currentMetaArtist: ""
38 property string currentMetaFile: ""
39 property string currentMetaTitle: ""
40 property int currentIndex: -1
41 property int duration: 1
42 readonly property bool isPlaying: player.playbackState === MediaPlayer.PlayingState
43 readonly property var playbackState: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.playbackState : MediaPlayer.StoppedState
44 property int position: 0
45 property alias repeat: settings.repeat
46 property alias shuffle: settings.shuffle
47 readonly property string source: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.source : ""
48 readonly property double volume: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.volume : 1.0
49
50 signal stopped()
51
52 Settings {
53 id: settings
54 category: "PlayerSettings"
55
56 property bool repeat: true
57 property bool shuffle: false
58 }
59
60 Connections {
61 target: trackQueue.model
62 onCountChanged: {
63 if (trackQueue.model.count === 1 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
64 player.currentIndex = 0;
65 setSource(Qt.resolvedUrl(trackQueue.model.get(0).filename))
66 } else if (trackQueue.model.count === 0 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
67 player.currentIndex = -1
68 setSource("")
69 }
70 }
71 }
72
73 function getSong(direction, startPlaying, fromControls) {
74 // Seek to start if threshold reached when selecting previous
75 if (direction === -1 && (player.position / 1000) > 5)
76 {
77 player.seek(0); // seek to start
78 return;
79 }
80
81 if (trackQueue.model.count == 0)
82 {
83 customdebug("No tracks in queue.");
84 return;
85 }
86
87 // default fromControls and startPlaying to true
88 fromControls = fromControls === undefined ? true : fromControls;
89 startPlaying = startPlaying === undefined ? true : startPlaying;
90 var newIndex;
91
92 console.log("currentIndex: " + currentIndex)
93 console.log("trackQueue.count: " + trackQueue.model.count)
94
95 // Do not shuffle if repeat is off and there is only one track in the queue
96 if (shuffle && !(trackQueue.model.count === 1 && !repeat)) {
97 var now = new Date();
98 var seed = now.getSeconds();
99
100 // trackQueue must be above 1 otherwise an infinite loop will occur
101 do {
102 newIndex = (Math.floor((trackQueue.model.count)
103 * Math.random(seed)));
104 } while (newIndex === currentIndex && trackQueue.model.count > 1)
105 } else {
106 if ((currentIndex < trackQueue.model.count - 1 && direction === 1 )
107 || (currentIndex > 0 && direction === -1)) {
108 newIndex = currentIndex + direction
109 } else if(direction === 1 && (repeat || fromControls)) {
110 newIndex = 0
111 } else if(direction === -1 && (repeat || fromControls)) {
112 newIndex = trackQueue.model.count - 1
113 }
114 else
115 {
116 player.stop()
117 return;
118 }
119 }
120
121 if (startPlaying) { // only start the track if told
122 playSong(trackQueue.model.get(newIndex).filename, newIndex)
123 }
124 else {
125 currentIndex = newIndex
126 setSource(Qt.resolvedUrl(trackQueue.model.get(newIndex).filename))
127 }
128
129 // Set index into queue
130 queueIndex = currentIndex
131 }
132
133 function nextSong(startPlaying, fromControls) {
134 getSong(1, startPlaying, fromControls)
135 }
136
137 function pause() {
138 mediaPlayerLoader.item.pause();
139 }
140
141 function play() {
142 mediaPlayerLoader.item.play();
143 }
144
145 function playSong(filepath, index) {
146 stop();
147 currentIndex = index;
148 queueIndex = index;
149 setSource(filepath);
150 play();
151 }
152
153 function previousSong(startPlaying) {
154 getSong(-1, startPlaying)
155 }
156
157 function seek(position) {
158 mediaPlayerLoader.item.seek(position);
159 }
160
161 function setSource(filepath) {
162 mediaPlayerLoader.item.source = Qt.resolvedUrl(filepath);
163 }
164
165 function setVolume(volume) {
166 mediaPlayerLoader.item.volume = volume
167 }
168
169 function stop() {
170 mediaPlayerLoader.item.stop();
171 }
172
173 function toggle() {
174 if (player.playbackState == MediaPlayer.PlayingState) {
175 pause()
176 }
177 else {
178 play()
179 }
180 }
181
182 Loader {
183 id: mediaPlayerLoader
184 asynchronous: true
185 sourceComponent: Component {
186 MediaPlayer {
187 muted: false
188
189 onDurationChanged: player.duration = duration
190 onPositionChanged: player.position = position
191
192 onSourceChanged: {
193 // Force invalid source to ""
194 if (source === undefined || source === false) {
195 source = ""
196 return
197 }
198
199 if (source.toString() === "") {
200 player.currentIndex = -1
201 player.stop()
202 }
203 else {
204 var obj;
205
206 if (source.toString().indexOf("file://") === 0) {
207 obj = musicStore.lookup(decodeFileURI(source.toString().substring(7)))
208 } else {
209 obj = musicStore.lookup(decodeFileURI(source.toString()))
210 }
211
212 // protect against null reponse from the lookup
213 if (obj !== null) {
214 // protect against undefined properties
215 player.currentMetaAlbum = obj.album || "";
216 player.currentMetaArt = obj.art || "";
217 player.currentMetaArtist = obj.author || "";
218 player.currentMetaFile = obj.filename || "";
219 player.currentMetaTitle = obj.title || "";
220 } else {
221 console.debug("Mediascanner lookup resulted in null object", source.toString())
222 }
223 }
224
225 console.log("Source: " + source.toString())
226 console.log("Index: " + player.currentIndex)
227 }
228
229 onStatusChanged: {
230 if (status == MediaPlayer.EndOfMedia) {
231 nextSong(true, false) // next track
232 }
233 }
234
235 onStopped: player.stopped()
236 }
237 }
238 }
239}
240
2410
=== modified file 'app/components/Queue.qml'
--- app/components/Queue.qml 2016-01-09 12:49:43 +0000
+++ app/components/Queue.qml 2016-01-26 00:01:55 +0000
@@ -31,39 +31,43 @@
31 anchors {31 anchors {
32 fill: parent32 fill: parent
33 }33 }
34 autoModelMove: false // ensures we use moveItem() not move() in onReorder
34 footer: Item {35 footer: Item {
35 height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)36 height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
36 }37 }
37 model: trackQueue.model38 model: player.mediaPlayer.playlist
38 objectName: "nowPlayingqueueList"39 objectName: "nowPlayingqueueList"
3940
40 onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")41 onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
4142
42 delegate: MusicListItem {43 delegate: MusicListItem {
43 id: queueListItem44 id: queueListItem
44 color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor45 color: player.mediaPlayer.playlist.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
46 height: units.gu(7)
45 leadingActions: ListItemActions {47 leadingActions: ListItemActions {
46 actions: [48 actions: [
47 Remove {49 Remove {
48 onTriggered: trackQueue.removeQueueList([index])50 onTriggered: player.mediaPlayer.playlist.removeItem(index)
49 }51 }
50 ]52 ]
51 }53 }
52 multiselectable: true54 multiselectable: true
53 objectName: "nowPlayingListItem" + index55 objectName: "nowPlayingListItem" + index
56 state: ""
54 reorderable: true57 reorderable: true
55 subtitle {58 subtitle {
56 objectName: "artistLabel"59 objectName: "artistLabel"
57 text: model.author60 text: metaModel.author
58 }61 }
59 title {62 title {
60 color: player.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music63 color: player.mediaPlayer.playlist.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
61 objectName: "titleLabel"64 objectName: "titleLabel"
62 text: model.title65 text: metaModel.title
63 }66 }
64 trailingActions: ListItemActions {67 trailingActions: ListItemActions {
65 actions: [68 actions: [
66 AddToPlaylist {69 AddToPlaylist {
70 modelOverride: metaModel // model is not exposed with metadata so use metaModel
67 }71 }
68 ]72 ]
69 delegate: ActionDelegate {73 delegate: ActionDelegate {
@@ -71,24 +75,18 @@
71 }75 }
72 }76 }
7377
78 property var metaModel: player.metaForSource(model.source)
79
74 onItemClicked: {80 onItemClicked: {
75 customdebug("File: " + model.filename) // debugger81 customdebug("File: " + model.source) // debugger
76 trackQueueClick(index); // toggle track state82 trackQueueClick(index);
77 }83 }
78 }84 }
7985
86
80 onReorder: {87 onReorder: {
81 Library.moveQueueItem(from, to);88 console.debug("Move: ", from, to);
8289
83 // Maintain currentIndex with current song90 player.mediaPlayer.playlist.moveItem(from, to);
84 if (from === player.currentIndex) {
85 player.currentIndex = to;
86 } else if (from < player.currentIndex && to >= player.currentIndex) {
87 player.currentIndex -= 1;
88 } else if (from > player.currentIndex && to <= player.currentIndex) {
89 player.currentIndex += 1;
90 }
91
92 queueIndex = player.currentIndex
93 }91 }
94}92}
9593
=== modified file 'app/components/ViewButton/PlayAllButton.qml'
--- app/components/ViewButton/PlayAllButton.qml 2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/PlayAllButton.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -30,5 +30,9 @@
3030
31 property var model31 property var model
3232
33 onClicked: trackClicked(model, 0) // play track33 onClicked: {
34 if (model.count > 0) {
35 trackClicked(model, 0) // play track
36 }
37 }
34}38}
3539
=== modified file 'app/components/ViewButton/QueueAllButton.qml'
--- app/components/ViewButton/QueueAllButton.qml 2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/QueueAllButton.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -28,7 +28,11 @@
2828
29 property var model29 property var model
3030
31 onClicked: addQueueFromModel(model)31 onClicked: {
32 if (model.count > 0) {
33 player.mediaPlayer.playlist.addItemsFromModel(model)
34 }
35 }
3236
33 Text {37 Text {
34 anchors {38 anchors {
3539
=== modified file 'app/components/ViewButton/ShuffleButton.qml'
--- app/components/ViewButton/ShuffleButton.qml 2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/ShuffleButton.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -28,7 +28,11 @@
2828
29 property var model29 property var model
3030
31 onClicked: shuffleModel(model)31 onClicked: {
32 if (model.count > 0) {
33 playRandomSong(model)
34 }
35 }
3236
33 Text {37 Text {
34 anchors {38 anchors {
3539
=== modified file 'app/logic/meta-database.js'
--- app/logic/meta-database.js 2015-06-20 18:01:59 +0000
+++ app/logic/meta-database.js 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -70,7 +70,7 @@
70 var ind = getNextIndex(tx);70 var ind = getNextIndex(tx);
7171
72 for (var i = 0; i < items.length; i++) {72 for (var i = 0; i < items.length; i++) {
73 tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i].filename]);73 tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i]]);
74 }74 }
75 }75 }
76 );76 );
@@ -156,9 +156,19 @@
156 db.transaction( function(tx) {156 db.transaction( function(tx) {
157 var rs = tx.executeSql("SELECT * FROM queue ORDER BY ind ASC");157 var rs = tx.executeSql("SELECT * FROM queue ORDER BY ind ASC");
158 for(var i = 0; i < rs.rows.length; i++) {158 for(var i = 0; i < rs.rows.length; i++) {
159 if (musicStore.lookup(decodeFileURI(rs.rows.item(i).filename)) != null) {159 var filename = rs.rows.item(i).filename;
160 res.push(makeDict(musicStore.lookup(decodeFileURI(rs.rows.item(i).filename))));160
161 if (filename !== null) {
162 // ms2 doesn't expect the URI scheme so strip file://
163 if (filename.indexOf("file://") == 0) {
164 filename = filename.substr(7);
165 }
166
167 if (musicStore.lookup(decodeFileURI(filename)) != null) {
168 res.push(Qt.resolvedUrl(filename));
169 }
161 }170 }
171
162 }172 }
163 });173 });
164 return res;174 return res;
165175
=== modified file 'app/logic/playlists.js'
--- app/logic/playlists.js 2015-06-20 17:57:58 +0000
+++ app/logic/playlists.js 2016-01-26 00:01:55 +0000
@@ -1,6 +1,8 @@
1/*1/*
2 * Copyright (C) 2013 Daniel Holm <d.holmen@gmail.com>2 * Copyright (C) 2013, 2016
3 * Victor Thompson <victor.thompson@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>
4 *6 *
5 * This program is free software; you can redistribute it and/or modify7 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by8 * it under the terms of the GNU General Public License as published by
@@ -230,6 +232,11 @@
230 for (j = 0; j < rs.rows.length; j++) {232 for (j = 0; j < rs.rows.length; j++) {
231 var dbItem = rs.rows.item(j)233 var dbItem = rs.rows.item(j)
232234
235 // ms2 doesn't expect the URI scheme so strip file://
236 if (dbItem.filename.indexOf("file://") === 0) {
237 dbItem.filename = dbItem.filename.substr(7);
238 }
239
233 if (musicStore.lookup(decodeFileURI(dbItem.filename)) === null) {240 if (musicStore.lookup(decodeFileURI(dbItem.filename)) === null) {
234 erroneousTracks.push(dbItem.i);241 erroneousTracks.push(dbItem.i);
235 } else {242 } else {
236243
=== modified file 'app/music-app.qml'
--- app/music-app.qml 2015-12-03 14:14:14 +0000
+++ app/music-app.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -22,7 +22,7 @@
22import Ubuntu.Components.Popups 1.322import Ubuntu.Components.Popups 1.3
23import Ubuntu.MediaScanner 0.123import Ubuntu.MediaScanner 0.1
24import Qt.labs.settings 1.024import Qt.labs.settings 1.0
25import QtMultimedia 5.025import QtMultimedia 5.6
26import QtQuick.LocalStorage 2.026import QtQuick.LocalStorage 2.0
27import QtGraphicalEffects 1.027import QtGraphicalEffects 1.0
28import "logic/stored-request.js" as StoredRequest28import "logic/stored-request.js" as StoredRequest
@@ -56,6 +56,11 @@
56 }56 }
57 }57 }
5858
59 Connections { // save the current queueIndex for when the app restarts
60 target: player.mediaPlayer.playlist
61 onCurrentIndexChanged: startupSettings.queueIndex = player.mediaPlayer.playlist.currentIndex
62 }
63
59 // Global keyboard shortcuts64 // Global keyboard shortcuts
60 focus: true65 focus: true
61 Keys.onPressed: {66 Keys.onPressed: {
@@ -73,33 +78,33 @@
7378
74 switch (event.key) {79 switch (event.key) {
75 case Qt.Key_Right: // Alt+Right Seek forward +10secs80 case Qt.Key_Right: // Alt+Right Seek forward +10secs
76 position = player.position + 10000 < player.duration81 position = player.mediaPlayer.position + 10000 < player.mediaPlayer.duration
77 ? player.position + 10000 : player.duration;82 ? player.mediaPlayer.position + 10000 : player.mediaPlayer.duration;
78 player.seek(position);83 player.mediaPlayer.seek(position);
79 break;84 break;
80 case Qt.Key_Left: // Alt+Left Seek backwards -10secs85 case Qt.Key_Left: // Alt+Left Seek backwards -10secs
81 position = player.position - 10000 > 086 position = player.mediaPlayer.position - 10000 > 0
82 ? player.position - 10000 : 0;87 ? player.mediaPlayer.position - 10000 : 0;
83 player.seek(position);88 player.mediaPlayer.seek(position);
84 break;89 break;
85 }90 }
86 }91 }
87 else if(event.modifiers === Qt.ControlModifier) {92 else if(event.modifiers === Qt.ControlModifier) {
88 switch (event.key) {93 switch (event.key) {
89 case Qt.Key_Left: // Ctrl+Left Previous Song94 case Qt.Key_Left: // Ctrl+Left Previous Song
90 player.previousSong(true);95 player.mediaPlayer.playlist.previousWrapper();
91 break;96 break;
92 case Qt.Key_Right: // Ctrl+Right Next Song97 case Qt.Key_Right: // Ctrl+Right Next Song
93 player.nextSong(true, true);98 player.mediaPlayer.playlist.nextWrapper();
94 break;99 break;
95 case Qt.Key_Up: // Ctrl+Up Volume up100 case Qt.Key_Up: // Ctrl+Up Volume up
96 player.volume = player.volume + .1 > 1 ? 1 : player.volume + .1101 player.mediaPlayer.volume = player.mediaPlayer.volume + .1 > 1 ? 1 : player.mediaPlayer.volume + .1
97 break;102 break;
98 case Qt.Key_Down: // Ctrl+Down Volume down103 case Qt.Key_Down: // Ctrl+Down Volume down
99 player.volume = player.volume - .1 < 0 ? 0 : player.volume - .1104 player.mediaPlayer.volume = player.mediaPlayer.volume - .1 < 0 ? 0 : player.mediaPlayer.volume - .1
100 break;105 break;
101 case Qt.Key_R: // Ctrl+R Repeat toggle106 case Qt.Key_R: // Ctrl+R Repeat toggle
102 player.repeat = !player.repeat107 player.mediaPlayer.repeat = !player.mediaPlayer.repeat
103 break;108 break;
104 case Qt.Key_F: // Ctrl+F Show Search popup109 case Qt.Key_F: // Ctrl+F Show Search popup
105 if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {110 if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
@@ -116,13 +121,13 @@
116 tabs.pushNowPlaying()121 tabs.pushNowPlaying()
117 break;122 break;
118 case Qt.Key_P: // Ctrl+P Toggle playing state123 case Qt.Key_P: // Ctrl+P Toggle playing state
119 player.toggle();124 player.mediaPlayer.toggle();
120 break;125 break;
121 case Qt.Key_Q: // Ctrl+Q Quit the app126 case Qt.Key_Q: // Ctrl+Q Quit the app
122 Qt.quit();127 Qt.quit();
123 break;128 break;
124 case Qt.Key_U: // Ctrl+U Shuffle toggle129 case Qt.Key_U: // Ctrl+U Shuffle toggle
125 player.shuffle = !player.shuffle130 player.mediaPlayer.shuffle = !player.mediaPlayer.shuffle
126 break;131 break;
127 }132 }
128 }133 }
@@ -152,15 +157,15 @@
152 id: nextAction157 id: nextAction
153 text: i18n.tr("Next")158 text: i18n.tr("Next")
154 keywords: i18n.tr("Next Track")159 keywords: i18n.tr("Next Track")
155 onTriggered: player.nextSong()160 onTriggered: player.mediaPlayer.playlist.nextWrapper()
156 }161 }
157 Action {162 Action {
158 id: playsAction163 id: playsAction
159 text: player.playbackState === MediaPlayer.PlayingState ?164 text: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
160 i18n.tr("Pause") : i18n.tr("Play")165 ? i18n.tr("Pause") : i18n.tr("Play")
161 keywords: player.playbackState === MediaPlayer.PlayingState ?166 keywords: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
162 i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")167 ? i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
163 onTriggered: player.toggle()168 onTriggered: player.mediaPlayer.toggle()
164 }169 }
165 Action {170 Action {
166 id: backAction171 id: backAction
@@ -175,13 +180,13 @@
175 id: prevAction180 id: prevAction
176 text: i18n.tr("Previous")181 text: i18n.tr("Previous")
177 keywords: i18n.tr("Previous Track")182 keywords: i18n.tr("Previous Track")
178 onTriggered: player.previousSong()183 onTriggered: player.mediaPlayer.playlist.previousWrapper()
179 }184 }
180 Action {185 Action {
181 id: stopAction186 id: stopAction
182 text: i18n.tr("Stop")187 text: i18n.tr("Stop")
183 keywords: i18n.tr("Stop Playback")188 keywords: i18n.tr("Stop Playback")
184 onTriggered: player.stop()189 onTriggered: player.mediaPlayer.stop()
185 }190 }
186191
187 actions: [nextAction, playsAction, prevAction, stopAction, backAction]192 actions: [nextAction, playsAction, prevAction, stopAction, backAction]
@@ -203,23 +208,6 @@
203 width: units.gu(100)208 width: units.gu(100)
204 height: units.gu(80)209 height: units.gu(80)
205210
206 WorkerModelLoader {
207 id: queueLoaderWorker
208 canLoad: false
209 model: trackQueue.model
210 syncFactor: 10
211
212 onPreLoadCompleteChanged: {
213 if (preLoadComplete) {
214 player.currentIndex = queueIndex
215
216 if (list[queueIndex] !== undefined) {
217 player.setSource(list[queueIndex].filename)
218 }
219 }
220 }
221 }
222
223 WorkerWaiter {211 WorkerWaiter {
224 id: waitForWorker212 id: waitForWorker
225 }213 }
@@ -229,14 +217,29 @@
229 customdebug("Version "+appVersion) // print the curren version217 customdebug("Version "+appVersion) // print the curren version
230218
231 Library.createRecent() // initialize recent219 Library.createRecent() // initialize recent
220 Library.createQueue() // create queue if it doesn't exist
232221
233 // initialize playlists222 // initialize playlists
234 Playlists.initializePlaylist()223 Playlists.initializePlaylist()
235224
236 if (!args.values.url) {225 if (!args.values.url) {
237 // allow the queue loader to start226 // load the previous queue as there are no args
238 queueLoaderWorker.canLoad = !Library.isQueueEmpty()227
239 queueLoaderWorker.list = Library.getQueue()228 // FIXME: load and save do not work yet pad.lv/1510225
229 // so use our localstorage method for now
230 // player.mediaPlayer.playlist.load("/home/phablet/.local/share/com.ubuntu.music/queue.m3u")
231 // use onloaded() and onLoadFailed() to confirm it is complete
232
233 if (!Library.isQueueEmpty()) {
234 console.debug("*** Restoring library queue");
235 player.mediaPlayer.playlist.addItems(Library.getQueue());
236
237 player.mediaPlayer.playlist.setCurrentIndex(queueIndex);
238 player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PausedState);
239 }
240 else {
241 console.debug("Queue is empty, not loading any recent tracks");
242 }
240 }243 }
241244
242 // everything else245 // everything else
@@ -301,23 +304,6 @@
301 }304 }
302 }305 }
303306
304 function addQueueFromModel(model)
305 {
306 // TODO: remove once playlists uses U1DB
307 if (model.hasOwnProperty("linkLibraryListModel")) {
308 model = model.linkLibraryListModel;
309 }
310
311 var items = []
312
313 for (var i=0; i < model.rowCount; i++) {
314 items.push(model.get(i, model.RoleModelData))
315 }
316
317 // Add model to queue storage
318 trackQueue.appendList(items)
319 }
320
321 // Converts an duration in ms to a formated string ("minutes:seconds")307 // Converts an duration in ms to a formated string ("minutes:seconds")
322 function durationToString(duration) {308 function durationToString(duration) {
323 var minutes = Math.floor((duration/1000) / 60);309 var minutes = Math.floor((duration/1000) / 60);
@@ -336,20 +322,13 @@
336 album: model.album,322 album: model.album,
337 art: model.art,323 art: model.art,
338 author: model.author,324 author: model.author,
339 filename: model.filename,325 filename: model.filename || model.source,
340 title: model.title326 title: model.title
341 };327 };
342 }328 }
343329
344 function trackClicked(model, index, play, clear) {330 // Clear the queue, queue this model and play the specific index
345 // Stop queue loading in the background331 function trackClicked(model, index, play) {
346 queueLoaderWorker.canLoad = false
347
348 if (queueLoaderWorker.processing > 0) {
349 waitForWorker.workerStop(queueLoaderWorker, trackClicked, [model, index, play, clear])
350 return;
351 }
352
353 // TODO: remove once playlists uses U1DB332 // TODO: remove once playlists uses U1DB
354 if (model.hasOwnProperty("linkLibraryListModel")) {333 if (model.hasOwnProperty("linkLibraryListModel")) {
355 model = model.linkLibraryListModel;334 model = model.linkLibraryListModel;
@@ -358,64 +337,50 @@
358 var file = Qt.resolvedUrl(model.get(index, model.RoleModelData).filename);337 var file = Qt.resolvedUrl(model.get(index, model.RoleModelData).filename);
359338
360 play = play === undefined ? true : play // default play to true339 play = play === undefined ? true : play // default play to true
361 clear = clear === undefined ? false : clear // force clear and will ignore player.toggle()340
362341 player.mediaPlayer.playlist.clearWrapper(); // clear the old model
363 if (!clear) {342 player.mediaPlayer.playlist.setCurrentIndex(index);
364 // If same track and on Now playing page then toggle343 player.mediaPlayer.playlist.addItemsFromModel(model);
365 if (mainPageStack.currentPage.title === i18n.tr("Now playing")
366 && trackQueue.model.get(player.currentIndex) !== undefined
367 && Qt.resolvedUrl(trackQueue.model.get(player.currentIndex).filename) === file) {
368 player.toggle()
369 return;
370 }
371 }
372
373 trackQueue.clear(); // clear the old model
374
375 addQueueFromModel(model);
376344
377 if (play) {345 if (play) {
378 player.playSong(file, index);346 // Set the pending state for the playlist
347 // this will be set once the currentIndex has been appened to the playlist
348 player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
379349
380 // Show the Now playing page and make sure the track is visible350 // Show the Now playing page and make sure the track is visible
381 tabs.pushNowPlaying();351 tabs.pushNowPlaying();
382 }352 }
383 else {
384 player.setSource(file);
385 }
386 }353 }
387354
355 // Play or pause a current track in the queue
356 // - the index has been tapped by the user
388 function trackQueueClick(index) {357 function trackQueueClick(index) {
389 if (player.currentIndex === index) {358 if (player.mediaPlayer.playlist.currentIndex === index) {
390 player.toggle();359 player.mediaPlayer.toggle();
391 }360 } else {
392 else {361 player.mediaPlayer.playlist.setCurrentIndex(index);
393 player.playSong(trackQueue.model.get(index).filename, index);362 player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
394 }363 }
395 }364 }
396365
397 function playRandomSong(shuffle)366 // Clear the queue and play a random track from this model
398 {367 // - user has selected "Shuffle" in album/artists or "Tap to play random"
399 trackQueue.clear();368 function playRandomSong(model)
400369 {
401 var now = new Date();370 // If no model is given use all the tracks
402 var seed = now.getSeconds();371 if (model === undefined) {
403 var index = Math.floor(allSongsModel.rowCount * Math.random(seed));372 model = allSongsModel;
404373 }
405 player.shuffle = shuffle === undefined ? true : shuffle;374
406375 player.mediaPlayer.playlist.clearWrapper();
407 trackClicked(allSongsModel, index, true)376 player.mediaPlayer.playlist.addItemsFromModel(model);
408 }
409
410 function shuffleModel(model)
411 {
412 var now = new Date();
413 var seed = now.getSeconds();
414 var index = Math.floor(model.count * Math.random(seed));
415
416 player.shuffle = true;377 player.shuffle = true;
417378
418 trackClicked(model, index, true)379 // Once the model count has been reached in the queue
380 // shuffle the model
381 player.mediaPlayer.playlist.setPendingShuffle(model.count);
382
383 tabs.pushNowPlaying();
419 }384 }
420385
421 // Wrapper function around decodeURIComponent() to prevent exceptions386 // Wrapper function around decodeURIComponent() to prevent exceptions
@@ -453,8 +418,15 @@
453 var removed = []418 var removed = []
454419
455 // Find tracks from the queue that aren't in ms2 anymore420 // Find tracks from the queue that aren't in ms2 anymore
456 for (i=0; i < trackQueue.model.count; i++) {421 for (i=0; i < player.mediaPlayer.playlist.count; i++) {
457 if (musicStore.lookup(decodeFileURI(trackQueue.model.get(i).filename)) === null) {422 var file = decodeFileURI(player.mediaPlayer.playlist.itemSource(i));
423
424 // ms2 doesn't expect the URI scheme so strip file://
425 if (file.indexOf("file://") === 0) {
426 file = file.substr(7);
427 }
428
429 if (musicStore.lookup(file) === null) {
458 removed.push(i)430 removed.push(i)
459 }431 }
460 }432 }
@@ -462,7 +434,7 @@
462 // If there are removed tracks then remove them from the queue and store434 // If there are removed tracks then remove them from the queue and store
463 if (removed.length > 0) {435 if (removed.length > 0) {
464 console.debug("Removed queue:", JSON.stringify(removed))436 console.debug("Removed queue:", JSON.stringify(removed))
465 trackQueue.removeQueueList(removed)437 player.mediaPlayer.playlist.removeItemsWrapper(removed.slice());
466 }438 }
467439
468 // Loop through playlists, getPlaylistTracks will remove any tracks that don't exist440 // Loop through playlists, getPlaylistTracks will remove any tracks that don't exist
@@ -529,7 +501,7 @@
529 if (status === SongsModel.Ready) {501 if (status === SongsModel.Ready) {
530 // Play album it tracks exist502 // Play album it tracks exist
531 if (rowCount > 0 && selectedAlbum) {503 if (rowCount > 0 && selectedAlbum) {
532 trackClicked(songsAlbumArtistModel, 0, true, true);504 trackClicked(songsAlbumArtistModel, 0, true);
533505
534 // Add album to recent list506 // Add album to recent list
535 Library.addRecent(songsAlbumArtistModel.get(0, SongsModel.RoleModelData).album, "album")507 Library.addRecent(songsAlbumArtistModel.get(0, SongsModel.RoleModelData).album, "album")
@@ -579,105 +551,6 @@
579 }551 }
580 }552 }
581553
582 // list of tracks on startup. This is just during development
583 LibraryListModel {
584 id: trackQueue
585 objectName: "trackQueue"
586
587 function append(listElement)
588 {
589 model.append(makeDict(listElement))
590 Library.addQueueItem(listElement.filename)
591 }
592
593 function appendList(items)
594 {
595 for (var i=0; i < items.length; i++) {
596 model.append(makeDict(items[i]))
597 }
598
599 Library.addQueueList(items)
600 }
601
602 function clear()
603 {
604 Library.clearQueue()
605 model.clear()
606
607 queueIndex = 0 // reset otherwise when you append and play 1 track it doesn't update correctly
608 }
609
610 // Optimised removeQueue for removing multiple tracks from the queue
611 function removeQueueList(items)
612 {
613 var i;
614
615 // Remove from the saved queue database
616 Library.removeQueueList(items)
617
618 // Sort the item indexes as loops below assume *numeric* sort
619 items.sort(function(a,b) { return a - b })
620
621 // Remove from the listmodel
622 var startCount = trackQueue.model.count
623
624 for (i=0; i < items.length; i++) {
625 // use diff in count as sometimes the row is removed from the model
626 trackQueue.model.remove(items[i] - (startCount - trackQueue.model.count));
627 }
628
629 // Update the currentIndex and playing status
630
631 if (trackQueue.model.count === 0) {
632 // Nothing in the queue so stop and pop the queue
633 player.stop()
634 mainPageStack.goBack()
635 } else if (items.indexOf(player.currentIndex) > -1) {
636 // Current track was removed
637
638 var newIndex;
639
640 // Find the first index that still exists before the currentIndex
641 for (i=player.currentIndex - 1; i > -1; i--) {
642 if (items.indexOf(i) === -1) {
643 break;
644 }
645 }
646
647 newIndex = i;
648
649 // Shuffle index down as tracks were removed before the current
650 for (i=newIndex; i > -1; i--) {
651 if (items.indexOf(i) !== -1) {
652 newIndex--;
653 }
654 }
655
656 // Set this as the current track
657 player.currentIndex = newIndex
658
659 // Play the next track
660 player.nextSong(player.isPlaying);
661 } else {
662 // Current track not in removed list
663 // Check if the index needs to be shuffled down due to removals
664
665 var before = 0
666
667 for (i=0; i < items.length; i++) {
668 if (items[i] < player.currentIndex) {
669 before++;
670 }
671 }
672
673 // Update the index
674 player.currentIndex -= before;
675 }
676
677 queueIndex = player.currentIndex; // ensure saved index is up to date
678 }
679 }
680
681 // TODO: list of playlists move to U1DB554 // TODO: list of playlists move to U1DB
682 // create the listmodel to use for playlists555 // create the listmodel to use for playlists
683 LibraryListModel {556 LibraryListModel {
684557
=== modified file 'app/ui/AddToPlaylist.qml'
--- app/ui/AddToPlaylist.qml 2016-01-12 00:30:08 +0000
+++ app/ui/AddToPlaylist.qml 2016-01-26 00:01:55 +0000
@@ -17,7 +17,6 @@
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */18 */
1919
20import QtMultimedia 5.0
21import QtQuick 2.420import QtQuick 2.4
22import Ubuntu.Components 1.321import Ubuntu.Components 1.3
23import QtQuick.LocalStorage 2.022import QtQuick.LocalStorage 2.0
2423
=== modified file 'app/ui/ContentHubExport.qml'
--- app/ui/ContentHubExport.qml 2015-11-02 04:56:53 +0000
+++ app/ui/ContentHubExport.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 20152 * Copyright (C) 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Victor Thompson <victor.thompson@gmail.com>4 * Victor Thompson <victor.thompson@gmail.com>
5 *5 *
@@ -136,25 +136,16 @@
136 delegate: MusicListItem {136 delegate: MusicListItem {
137 id: track137 id: track
138 objectName: "tracksPageListItem" + index138 objectName: "tracksPageListItem" + index
139 column: Column {
140 Label {
141 id: trackTitle
142 color: styleMusic.common.music
143 fontSize: "small"
144 objectName: "tracktitle"
145 text: model.title
146 }
147
148 Label {
149 id: trackArtist
150 color: styleMusic.common.subtitle
151 fontSize: "x-small"
152 text: model.author
153 }
154 }
155 height: units.gu(7)139 height: units.gu(7)
156 imageSource: {"art": model.art}140 imageSource: {"art": model.art}
157 multiselectable: true141 multiselectable: true
142 subtitle {
143 text: model.author
144 }
145 title {
146 objectName: "tracktitle"
147 text: model.title
148 }
158149
159 onSelectedChanged: {150 onSelectedChanged: {
160 if (singular) {151 if (singular) {
161152
=== modified file 'app/ui/NowPlaying.qml'
--- app/ui/NowPlaying.qml 2015-11-05 01:16:43 +0000
+++ app/ui/NowPlaying.qml 2016-01-26 00:01:55 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright (C) 2013, 2014, 20152 * Copyright (C) 2013, 2014, 2015, 2016
3 * Andrew Hayzen <ahayzen@gmail.com>3 * Andrew Hayzen <ahayzen@gmail.com>
4 * Daniel Holm <d.holmen@gmail.com>4 * Daniel Holm <d.holmen@gmail.com>
5 * Victor Thompson <victor.thompson@gmail.com>5 * Victor Thompson <victor.thompson@gmail.com>
@@ -61,12 +61,12 @@
6161
62 // Ensure that the listview has loaded before attempting to positionAt62 // Ensure that the listview has loaded before attempting to positionAt
63 function ensureListViewLoaded() {63 function ensureListViewLoaded() {
64 if (queueListLoader.item.count === trackQueue.model.count) {64 if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
65 positionAt(player.currentIndex);65 positionAt(player.mediaPlayer.playlist.currentIndex);
66 } else {66 } else {
67 queueListLoader.item.onCountChanged.connect(function() {67 queueListLoader.item.onCountChanged.connect(function() {
68 if (queueListLoader.item.count === trackQueue.model.count) {68 if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
69 positionAt(player.currentIndex);69 positionAt(player.mediaPlayer.playlist.currentIndex);
70 }70 }
71 })71 })
72 }72 }
@@ -99,29 +99,30 @@
99 name: "default"99 name: "default"
100 actions: [100 actions: [
101 Action {101 Action {
102 enabled: trackQueue.model.count > 0102 enabled: !player.mediaPlayer.playlist.empty
103 iconName: "add-to-playlist"103 iconName: "add-to-playlist"
104 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)104 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
105 text: i18n.tr("Add to playlist")105 text: i18n.tr("Add to playlist")
106 visible: !isListView
107
106 onTriggered: {108 onTriggered: {
107 var items = []109 var items = []
108110
109 items.push(makeDict(trackQueue.model.get(player.currentIndex)));111 items.push(makeDict(player.metaForSource(player.mediaPlayer.playlist.currentItemSource)));
110112
111 mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),113 mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
112 {"chosenElements": items})114 {"chosenElements": items})
113 }115 }
114 },116 },
115 Action {117 Action {
116 enabled: trackQueue.model.count > 0118 enabled: !player.mediaPlayer.playlist.empty
117 iconName: "delete"119 iconName: "delete"
118 objectName: "clearQueue"120 objectName: "clearQueue"
119 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)121 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
120 text: i18n.tr("Clear queue")122 text: i18n.tr("Clear queue")
121 onTriggered: {123 visible: isListView
122 mainPageStack.goBack()124
123 trackQueue.clear()125 onTriggered: player.mediaPlayer.playlist.clearWrapper()
124 }
125 }126 }
126 ]127 ]
127 PropertyChanges {128 PropertyChanges {
@@ -140,13 +141,14 @@
140 // Remove the tracks from the queue141 // Remove the tracks from the queue
141 // Use slice() to copy the list142 // Use slice() to copy the list
142 // so that the indexes don't change as they are removed143 // so that the indexes don't change as they are removed
143 trackQueue.removeQueueList(selectedIndices.slice())144 player.mediaPlayer.playlist.removeItemsWrapper(selectedIndices.slice());
144 }145 }
145 }146 }
146 ]147 ]
147148
148 Loader {149 Loader {
149 anchors {150 anchors {
151 bottom: nowPlayingToolbarLoader.top
150 left: parent.left152 left: parent.left
151 right: parent.right153 right: parent.right
152 top: parent.top154 top: parent.top
@@ -155,7 +157,6 @@
155157
156 property real headerHeight: units.gu(10.125) // FIXME: 10.125 is the header.height with the page sections158 property real headerHeight: units.gu(10.125) // FIXME: 10.125 is the header.height with the page sections
157159
158 height: parent.height - headerHeight - units.gu(9.5)
159 source: "../components/NowPlayingFullView.qml"160 source: "../components/NowPlayingFullView.qml"
160 visible: !isListView161 visible: !isListView
161 }162 }
@@ -163,8 +164,11 @@
163 Loader {164 Loader {
164 id: queueListLoader165 id: queueListLoader
165 anchors {166 anchors {
166 bottomMargin: nowPlayingToolbarLoader.height + units.gu(2)167 bottom: nowPlayingToolbarLoader.top
167 fill: parent168 bottomMargin: units.gu(2)
169 left: parent.left
170 right: parent.right
171 top: parent.top
168 topMargin: units.gu(2)172 topMargin: units.gu(2)
169 }173 }
170 asynchronous: true174 asynchronous: true
@@ -182,4 +186,13 @@
182 height: units.gu(10)186 height: units.gu(10)
183 source: "../components/NowPlayingToolbar.qml"187 source: "../components/NowPlayingToolbar.qml"
184 }188 }
189
190 Connections {
191 target: player.mediaPlayer.playlist
192 onEmptyChanged: {
193 if (player.mediaPlayer.playlist.empty) {
194 mainPageStack.goBack()
195 }
196 }
197 }
185}198}
186199
=== modified file 'app/ui/Playlists.qml'
--- app/ui/Playlists.qml 2016-01-12 00:30:08 +0000
+++ app/ui/Playlists.qml 2016-01-26 00:01:55 +0000
@@ -20,7 +20,6 @@
2020
21import QtQuick 2.421import QtQuick 2.4
22import Ubuntu.Components 1.322import Ubuntu.Components 1.3
23import QtMultimedia 5.0
24import QtQuick.LocalStorage 2.023import QtQuick.LocalStorage 2.0
25import "../logic/playlists.js" as Playlists24import "../logic/playlists.js" as Playlists
26import "../components"25import "../components"
2726
=== modified file 'app/ui/Recent.qml'
--- app/ui/Recent.qml 2016-01-12 00:30:08 +0000
+++ app/ui/Recent.qml 2016-01-26 00:01:55 +0000
@@ -21,7 +21,6 @@
21import Ubuntu.Components 1.321import Ubuntu.Components 1.3
22import Ubuntu.MediaScanner 0.122import Ubuntu.MediaScanner 0.1
23import Ubuntu.Thumbnailer 0.123import Ubuntu.Thumbnailer 0.1
24import QtMultimedia 5.0
25import QtQuick.LocalStorage 2.024import QtQuick.LocalStorage 2.0
26import "../logic/meta-database.js" as Library25import "../logic/meta-database.js" as Library
27import "../logic/playlists.js" as Playlists26import "../logic/playlists.js" as Playlists
2827
=== modified file 'app/ui/Songs.qml'
--- app/ui/Songs.qml 2016-01-09 12:49:43 +0000
+++ app/ui/Songs.qml 2016-01-26 00:01:55 +0000
@@ -21,7 +21,6 @@
21import Ubuntu.Components 1.321import Ubuntu.Components 1.3
22import Ubuntu.MediaScanner 0.122import Ubuntu.MediaScanner 0.1
23import Ubuntu.Thumbnailer 0.123import Ubuntu.Thumbnailer 0.1
24import QtMultimedia 5.0
25import QtQuick.LocalStorage 2.024import QtQuick.LocalStorage 2.0
26import "../logic/playlists.js" as Playlists25import "../logic/playlists.js" as Playlists
27import "../components"26import "../components"
@@ -107,8 +106,8 @@
107106
108 onItemClicked: {107 onItemClicked: {
109 if (songsPage.state === "search") { // only play single track when searching108 if (songsPage.state === "search") { // only play single track when searching
110 trackQueue.clear()109 player.mediaPlayer.playlist.clearWrapper();
111 trackQueue.append(songsModelFilter.get(index))110 player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(songsModelFilter.get(index).filename));
112 trackQueueClick(0)111 trackQueueClick(0)
113 } else {112 } else {
114 trackClicked(songsModelFilter, index) // play track113 trackClicked(songsModelFilter, index) // play track
115114
=== modified file 'debian/changelog'
--- debian/changelog 2016-01-12 00:26:51 +0000
+++ debian/changelog 2016-01-26 00:01:55 +0000
@@ -1,9 +1,12 @@
1music-app (2.3) UNRELEASED; urgency=medium1music-app (2.3) UNRELEASED; urgency=medium
2 2
3 [ Andrew Hayzen ]3 [ Andrew Hayzen ]
4 * Release 2.2ubuntu2 and start on 2.34 * Release 2.2ubuntu2 and start on 2.3
5 * Use ListItemLayout for listitems to improve performance and match design guidelines (LP: #1526247).5 * Use ListItemLayout for listitems to improve performance and match design guidelines (LP: #1526247).
6 * Switch to using Qt GridView instead of custom CardView6 * Switch to using Qt GridView instead of custom CardView
7 * Add support for media-hub background playlists
8 * Bump framework to 15.04.3-qml
9 * Bump QtMultimedia import to 5.6
710
8 [ Ken VanDine ]11 [ Ken VanDine ]
9 * Install the content-hub json file in the correct place for peer registry12 * Install the content-hub json file in the correct place for peer registry
1013
=== modified file 'debian/control'
--- debian/control 2014-10-22 16:24:01 +0000
+++ debian/control 2016-01-26 00:01:55 +0000
@@ -18,6 +18,7 @@
18 gstreamer1.0-fluendo-mp3,18 gstreamer1.0-fluendo-mp3,
19 qmlscene,19 qmlscene,
20 qml-module-qt-labs-settings,20 qml-module-qt-labs-settings,
21 qml-module-qtmultimedia (>=5.5.1-1ubuntu2),
21 qtdeclarative5-ubuntu-content0.1,22 qtdeclarative5-ubuntu-content0.1,
22 qtdeclarative5-localstorage-plugin,23 qtdeclarative5-localstorage-plugin,
23 qtdeclarative5-particles-plugin,24 qtdeclarative5-particles-plugin,
2425
=== modified file 'manifest.json.in'
--- manifest.json.in 2015-12-03 14:14:14 +0000
+++ manifest.json.in 2016-01-26 00:01:55 +0000
@@ -1,7 +1,7 @@
1{1{
2 "architecture": "all",2 "architecture": "all",
3 "description": "A music application for ubuntu",3 "description": "A music application for ubuntu",
4 "framework": "ubuntu-sdk-15.04.1-qml",4 "framework": "ubuntu-sdk-15.04.3-qml",
5 "hooks": {5 "hooks": {
6 "music": {6 "music": {
7 "apparmor": "apparmor.json",7 "apparmor": "apparmor.json",
88
=== modified file 'tests/autopilot/music_app/__init__.py'
--- tests/autopilot/music_app/__init__.py 2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/__init__.py 2016-01-26 00:01:55 +0000
@@ -54,7 +54,6 @@
54 # Use only objectName due to bug 1350532 as it is MainView1254 # Use only objectName due to bug 1350532 as it is MainView12
55 self.main_view = self.app.wait_select_single(55 self.main_view = self.app.wait_select_single(
56 objectName="musicMainView")56 objectName="musicMainView")
57 self.player = self.app.select_single(Player, objectName='player')
5857
59 def get_add_to_playlist_page(self):58 def get_add_to_playlist_page(self):
60 return self.app.wait_select_single(AddToPlaylist,59 return self.app.wait_select_single(AddToPlaylist,
@@ -95,8 +94,7 @@
95 Playlists, objectName='playlistsPage')94 Playlists, objectName='playlistsPage')
9695
97 def get_queue_count(self):96 def get_queue_count(self):
98 return self.main_view.select_single("LibraryListModel",97 return self.player.count
99 objectName="trackQueue").count
10098
101 def get_songs_view(self):99 def get_songs_view(self):
102 return self.app.wait_select_single(SongsView, objectName="songsPage")100 return self.app.wait_select_single(SongsView, objectName="songsPage")
@@ -121,6 +119,11 @@
121 objectName="LoadingSpinner").running and119 objectName="LoadingSpinner").running and
122 self.main_view.select_single("*", "allSongsModel").populated)120 self.main_view.select_single("*", "allSongsModel").populated)
123121
122 @property
123 def player(self):
124 # Get new player each time as data changes (eg currentMeta)
125 return self.app.select_single(Player, objectName='player')
126
124 def populate_queue(self):127 def populate_queue(self):
125 tracksPage = self.get_songs_page() # switch to track tab128 tracksPage = self.get_songs_page() # switch to track tab
126129
@@ -263,15 +266,16 @@
263class Player(UbuntuUIToolkitCustomProxyObjectBase):266class Player(UbuntuUIToolkitCustomProxyObjectBase):
264 """Autopilot helper for Player"""267 """Autopilot helper for Player"""
265268
269 @property
270 def currentMeta(self):
271 return self.select_single("*", objectName="currentMeta")
272
266273
267class NowPlaying(MusicPage):274class NowPlaying(MusicPage):
268 """ Autopilot helper for now playing page """275 """ Autopilot helper for now playing page """
269 def __init__(self, *args):276 def __init__(self, *args):
270 super(NowPlaying, self).__init__(*args)277 super(NowPlaying, self).__init__(*args)
271278
272 root = self.get_root_instance()
273 self.player = root.select_single(Player, objectName="player")
274
275 self.visible.wait_for(True)279 self.visible.wait_for(True)
276280
277 @ensure_now_playing_full281 @ensure_now_playing_full
@@ -279,6 +283,9 @@
279 def click_forward_button(self):283 def click_forward_button(self):
280 return self.wait_select_single("*", objectName="forwardShape")284 return self.wait_select_single("*", objectName="forwardShape")
281285
286 def click_full_view(self):
287 self.main_view.get_header().switch_to_section_by_index(0)
288
282 @ensure_now_playing_full289 @ensure_now_playing_full
283 @click_object290 @click_object
284 def click_play_button(self):291 def click_play_button(self):
@@ -289,6 +296,9 @@
289 def click_previous_button(self):296 def click_previous_button(self):
290 return self.wait_select_single("*", objectName="previousShape")297 return self.wait_select_single("*", objectName="previousShape")
291298
299 def click_queue_view(self):
300 self.main_view.get_header().switch_to_section_by_index(1)
301
292 @ensure_now_playing_full302 @ensure_now_playing_full
293 @click_object303 @click_object
294 def click_repeat_button(self):304 def click_repeat_button(self):
@@ -299,17 +309,21 @@
299 def click_shuffle_button(self):309 def click_shuffle_button(self):
300 return self.wait_select_single("*", objectName="shuffleShape")310 return self.wait_select_single("*", objectName="shuffleShape")
301311
302 def click_full_view(self):312 @click_object
303 self.main_view.get_header().switch_to_section_by_index(0)313 def click_track(self, i):
304314 return self.get_track(i)
305 def click_queue_view(self):
306 self.main_view.get_header().switch_to_section_by_index(1)
307315
308 @ensure_now_playing_list316 @ensure_now_playing_list
309 def get_track(self, i):317 def get_track(self, i):
310 return (self.wait_select_single(MusicListItem,318 return (self.wait_select_single(MusicListItem,
311 objectName="nowPlayingListItem" + str(i)))319 objectName="nowPlayingListItem" + str(i)))
312320
321 @property
322 def player(self):
323 # Get new player each time as data changes (eg currentMeta)
324 root = self.get_root_instance()
325 return root.select_single(Player, objectName="player")
326
313 @ensure_now_playing_full327 @ensure_now_playing_full
314 def seek_to(self, percentage):328 def seek_to(self, percentage):
315 progress_bar = self.wait_select_single(329 progress_bar = self.wait_select_single(
@@ -415,6 +429,11 @@
415429
416430
417class Dialog(UbuntuUIToolkitCustomProxyObjectBase):431class Dialog(UbuntuUIToolkitCustomProxyObjectBase):
432 def __init__(self, *args):
433 super(Dialog, self).__init__(*args)
434
435 self.visible.wait_for(True)
436
418 @click_object437 @click_object
419 def click_new_playlist_dialog_create_button(self):438 def click_new_playlist_dialog_create_button(self):
420 return self.wait_select_single(439 return self.wait_select_single(
421440
=== modified file 'tests/autopilot/music_app/tests/test_music.py'
--- tests/autopilot/music_app/tests/test_music.py 2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/tests/test_music.py 2016-01-26 00:01:55 +0000
@@ -77,9 +77,9 @@
77 self.app.populate_queue() # populate queue77 self.app.populate_queue() # populate queue
7878
79 # Check current meta data is correct79 # Check current meta data is correct
80 self.assertThat(self.player.currentMetaTitle,80 self.assertThat(self.player.currentMeta.title,
81 Eventually(Equals(self.tracks[0]["title"])))81 Eventually(Equals(self.tracks[0]["title"])))
82 self.assertThat(self.player.currentMetaArtist,82 self.assertThat(self.player.currentMeta.author,
83 Eventually(Equals(self.tracks[0]["artist"])))83 Eventually(Equals(self.tracks[0]["artist"])))
8484
85 def test_play_pause_library(self):85 def test_play_pause_library(self):
@@ -158,52 +158,44 @@
158158
159 now_playing_page = self.app.get_now_playing_page()159 now_playing_page = self.app.get_now_playing_page()
160160
161 # check the track was playing
162 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
163
164 # select pause and check the player has stopped
165 now_playing_page.click_play_button()
166 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
167
161 # save original song data for later168 # save original song data for later
162 org_title = self.player.currentMetaTitle169 orig_title = self.player.currentMeta.title
163 org_artist = self.player.currentMetaArtist170 orig_artist = self.player.currentMeta.author
164171 logger.debug("Original Song %s, %s" % (orig_title, orig_artist))
165 # check original track
166 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
167 logger.debug("Original Song %s, %s" % (org_title, org_artist))
168
169 # select pause and check the player has stopped
170 now_playing_page.click_play_button()
171 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
172172
173 now_playing_page.set_shuffle(False) # ensure shuffe is off173 now_playing_page.set_shuffle(False) # ensure shuffe is off
174174
175 # goal is to go back and forth and ensure 2 different songs175 # goal is to go back and forth and ensure 2 different songs
176 now_playing_page.click_forward_button()176 now_playing_page.click_forward_button()
177 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
178177
179 # select pause and check the player has stopped178 # check the player is still stopped
180 now_playing_page.click_play_button()
181 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))179 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
182180
183 # ensure different song181 # ensure different song
184 self.assertThat(self.player.currentMetaTitle,182 self.assertThat(self.player.currentMeta.title,
185 Eventually(NotEquals(org_title)))183 Eventually(NotEquals(orig_title)))
186 self.assertThat(self.player.currentMetaArtist,184 self.assertThat(self.player.currentMeta.author,
187 Eventually(NotEquals(org_artist)))185 Eventually(NotEquals(orig_artist)))
188186
189 logger.debug("Next Song %s, %s" % (self.player.currentMetaTitle,187 logger.debug("Next Song %s, %s" % (self.player.currentMeta.title,
190 self.player.currentMetaArtist))188 self.player.currentMeta.author))
191189
192 now_playing_page.seek_to(0) # seek to 0 (start)190 # select previous and ensure the track is stopped
193
194 # select previous and ensure the track is playing
195 now_playing_page.click_previous_button()191 now_playing_page.click_previous_button()
196 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
197
198 # select pause and check the player has stopped
199 now_playing_page.click_play_button()
200 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))192 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
201193
202 # ensure we're back to original song194 # ensure we're back to original song
203 self.assertThat(self.player.currentMetaTitle,195 self.assertThat(self.player.currentMeta.title,
204 Eventually(Equals(org_title)))196 Eventually(Equals(orig_title)))
205 self.assertThat(self.player.currentMetaArtist,197 self.assertThat(self.player.currentMeta.author,
206 Eventually(Equals(org_artist)))198 Eventually(Equals(orig_artist)))
207199
208 def test_mp3(self):200 def test_mp3(self):
209 """ Test that mp3 "plays" or at least doesn't crash on load """201 """ Test that mp3 "plays" or at least doesn't crash on load """
@@ -231,7 +223,7 @@
231 Eventually(Equals(initial_tracks_count + 1)))223 Eventually(Equals(initial_tracks_count + 1)))
232224
233 # Ensure the current track is mp3225 # Ensure the current track is mp3
234 self.assertThat(self.player.source.endswith("mp3"),226 self.assertThat(self.player.currentItemSource.endswith("mp3"),
235 Equals(True))227 Equals(True))
236228
237 # Start playing the track (click from toolbar)229 # Start playing the track (click from toolbar)
@@ -244,9 +236,9 @@
244 toolbar.click_play_button()236 toolbar.click_play_button()
245237
246 # Check current meta data is correct238 # Check current meta data is correct
247 self.assertThat(self.player.currentMetaTitle,239 self.assertThat(self.player.currentMeta.title,
248 Eventually(Equals(self.tracks[i]["title"])))240 Eventually(Equals(self.tracks[i]["title"])))
249 self.assertThat(self.player.currentMetaArtist,241 self.assertThat(self.player.currentMeta.author,
250 Eventually(Equals(self.tracks[i]["artist"])))242 Eventually(Equals(self.tracks[i]["artist"])))
251243
252 def test_shuffle(self):244 def test_shuffle(self):
@@ -258,30 +250,31 @@
258250
259 now_playing_page = self.app.get_now_playing_page()251 now_playing_page = self.app.get_now_playing_page()
260252
253 now_playing_page.set_repeat(True)
254 now_playing_page.set_shuffle(True)
255
261 # pause the track if it is playing256 # pause the track if it is playing
262 if self.player.isPlaying:257 if self.player.isPlaying:
263 now_playing_page.click_play_button()258 now_playing_page.click_play_button()
264259
265 self.player.isPlaying.wait_for(False)260 self.player.isPlaying.wait_for(False)
266261
267 now_playing_page.set_shuffle(True) # enable shuffle
268
269 # save original song metadata
270 org_title = self.player.currentMetaTitle
271 org_artist = self.player.currentMetaArtist
272
273 logger.debug("Original Song %s, %s" % (org_title, org_artist))
274
275 count = 0262 count = 0
263 previous_index = -1
276264
277 # loop while the track is the same if different then a shuffle occurred265 # Keep going until the index is not previous + 1 (with wrapping)
278 while (org_title == self.player.currentMetaTitle and266 # or previous == currentIndex (to ensure shuffle is working)
279 org_artist == self.player.currentMetaArtist):267 while ((previous_index + 1) % self.player.count ==
268 self.player.currentIndex or
269 previous_index == self.player.currentIndex):
280 logger.debug("count %s" % (count))270 logger.debug("count %s" % (count))
281271
282 # check count is valid272 # check count is valid
283 self.assertThat(count, LessThan(100))273 self.assertThat(count, LessThan(100))
284274
275 # store this index as the previous
276 previous_index = self.player.currentIndex
277
285 # select next track278 # select next track
286 now_playing_page.click_forward_button()279 now_playing_page.click_forward_button()
287280
@@ -289,28 +282,13 @@
289 if self.player.isPlaying:282 if self.player.isPlaying:
290 now_playing_page.click_play_button()283 now_playing_page.click_play_button()
291284
292 # check it is paused285 self.player.isPlaying.wait_for(False)
293 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))286
294287 # toggle shuffle to increase random
295 # save current file so we can check it goes back288 now_playing_page.set_shuffle(False)
296 source = self.player.currentMetaFile289 now_playing_page.set_shuffle(True)
297290
298 # select previous track while will break if this previous track291 count += 1
299 # is different and therefore a shuffle has occurred
300 now_playing_page.click_previous_button()
301
302 # pause the track if it is playing
303 if self.player.isPlaying:
304 now_playing_page.click_play_button()
305
306 # check it is paused
307 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
308
309 # check the file has actually changed
310 self.assertThat(self.player.currentMetaFile,
311 Eventually(NotEquals(source)))
312
313 count += 1 # increment count
314292
315 def test_show_albums_page(self):293 def test_show_albums_page(self):
316 """tests navigating to the Albums tab and displaying the album page"""294 """tests navigating to the Albums tab and displaying the album page"""
@@ -648,7 +626,7 @@
648 now_playing_page.click_forward_button()626 now_playing_page.click_forward_button()
649627
650 # Make sure we loop back to first song after last song ends628 # Make sure we loop back to first song after last song ends
651 self.assertThat(self.player.currentMetaTitle,629 self.assertThat(self.player.currentMeta.title,
652 Eventually(Equals(self.tracks[0]["title"])))630 Eventually(Equals(self.tracks[0]["title"])))
653 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))631 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
654632
@@ -667,7 +645,7 @@
667 now_playing_page.click_forward_button()645 now_playing_page.click_forward_button()
668646
669 # Make sure we loop back to first song after last song ends647 # Make sure we loop back to first song after last song ends
670 self.assertThat(self.player.currentMetaTitle,648 self.assertThat(self.player.currentMeta.title,
671 Eventually(Equals(self.tracks[0]["title"])))649 Eventually(Equals(self.tracks[0]["title"])))
672 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))650 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
673651
@@ -681,16 +659,16 @@
681 now_playing_page.set_shuffle(False)659 now_playing_page.set_shuffle(False)
682 now_playing_page.set_repeat(True)660 now_playing_page.set_repeat(True)
683661
684 initial_song = self.player.currentMetaTitle662 initial_song = self.player.currentMeta.title
685 now_playing_page.click_previous_button()663 now_playing_page.click_previous_button()
686664
687 # If we're far enough into a song, pressing prev just takes us to the665 # If we're far enough into a song, pressing prev just takes us to the
688 # beginning of that track. In that case, hit prev again to actually666 # beginning of that track. In that case, hit prev again to actually
689 # skip over the track.667 # skip over the track.
690 if self.player.currentMetaTitle == initial_song:668 if self.player.currentMeta.title == initial_song:
691 now_playing_page.click_previous_button()669 now_playing_page.click_previous_button()
692670
693 self.assertThat(self.player.currentMetaTitle,671 self.assertThat(self.player.currentMeta.title,
694 Eventually(Equals(self.tracks[-1]["title"])))672 Eventually(Equals(self.tracks[-1]["title"])))
695 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))673 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
696674
@@ -702,19 +680,23 @@
702 now_playing_page = self.app.get_now_playing_page()680 now_playing_page = self.app.get_now_playing_page()
703681
704 self.player.isPlaying.wait_for(True) # ensure the track is playing682 self.player.isPlaying.wait_for(True) # ensure the track is playing
705 self.player.position.wait_for(GreaterThan(5000)) # wait until > 5s683
684 # wait until > 5s
685 self.player.position.wait_for(GreaterThan(5000))
706686
707 now_playing_page.click_play_button() # pause the track687 now_playing_page.click_play_button() # pause the track
708 self.player.isPlaying.wait_for(False) # ensure the track has paused688 self.player.isPlaying.wait_for(False) # ensure the track has paused
709689
710 source = self.player.source # store current source690 source = self.player.currentMeta.filename # store current source
711691
712 now_playing_page.click_previous_button() # click previous692 now_playing_page.click_previous_button() # click previous
713693
714 # resume the track (to ensure position updates)694 # resume the track (to ensure position updates)
715 now_playing_page.click_play_button()695 now_playing_page.click_play_button()
716696
717 self.player.position.wait_for(LessThan(5000)) # wait until < 5s697 # wait until < 5s
698 self.player.position.wait_for(LessThan(5000))
718699
719 # Check that the source is the same700 # Check that the source is the same
720 self.assertThat(self.player.source, Eventually(Equals(source)))701 self.assertThat(self.player.currentMeta.filename,
702 Eventually(Equals(source)))

Subscribers

People subscribed via source and target branches

to all changes: