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
1=== modified file 'app/components/BlurredBackground.qml'
2--- app/components/BlurredBackground.qml 2015-08-12 23:36:44 +0000
3+++ app/components/BlurredBackground.qml 2016-01-26 00:01:55 +0000
4@@ -1,5 +1,5 @@
5 /*
6- * Copyright (C) 2013, 2014, 2015
7+ * Copyright (C) 2013, 2014, 2015, 2016
8 * Andrew Hayzen <ahayzen@gmail.com>
9 * Daniel Holm <d.holmen@gmail.com>
10 * Victor Thompson <victor.thompson@gmail.com>
11@@ -25,7 +25,7 @@
12 Item {
13 width: parent.width
14
15- property string art // : player.currentMetaFile === "" ? Qt.resolvedUrl("../graphics/music-app-cover@30.png") : player.currentMetaArt
16+ property string art
17
18 // dark layer
19 Rectangle {
20
21=== modified file 'app/components/Flickables/MultiSelectListView.qml'
22--- app/components/Flickables/MultiSelectListView.qml 2015-10-18 18:16:05 +0000
23+++ app/components/Flickables/MultiSelectListView.qml 2016-01-26 00:01:55 +0000
24@@ -1,5 +1,5 @@
25 /*
26- * Copyright (C) 2013, 2014, 2015
27+ * Copyright (C) 2013, 2014, 2015, 2016
28 * Andrew Hayzen <ahayzen@gmail.com>
29 * Daniel Holm <d.holmen@gmail.com>
30 * Victor Thompson <victor.thompson@gmail.com>
31@@ -25,6 +25,10 @@
32 // so we need to expose if in multiselect mode for the header states
33 state: ViewItems.selectMode ? "multiselectable" : "normal"
34
35+ // Describes if model.move() should be called when a list item drag is completed
36+ // this is not required on the Queue as onReorder performs playlist.moveItem()
37+ property bool autoModelMove: true
38+
39 signal clearSelection()
40 signal closeSelection()
41 signal reorder(int from, int to)
42@@ -63,7 +67,10 @@
43 if (event.status == ListItemDrag.Moving) {
44 event.accept = false
45 } else if (event.status == ListItemDrag.Dropped) {
46- model.move(event.from, event.to, 1);
47+ if (autoModelMove) { // check the model should auto move
48+ model.move(event.from, event.to, 1);
49+ }
50+
51 reorder(event.from, event.to)
52 }
53 }
54
55=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
56--- app/components/HeadState/MultiSelectHeadState.qml 2015-10-18 18:16:05 +0000
57+++ app/components/HeadState/MultiSelectHeadState.qml 2016-01-26 00:01:55 +0000
58@@ -1,5 +1,5 @@
59 /*
60- * Copyright (C) 2015
61+ * Copyright (C) 2015, 2016
62 * Andrew Hayzen <ahayzen@gmail.com>
63 * Victor Thompson <victor.thompson@gmail.com>
64 *
65@@ -59,14 +59,14 @@
66 visible: addToQueue
67
68 onTriggered: {
69- var items = []
70+ var items = [];
71 var indicies = listview.getSelectedIndices();
72
73 for (var i=0; i < indicies.length; i++) {
74- items.push(listview.model.get(indicies[i], listview.model.RoleModelData));
75+ items.push(Qt.resolvedUrl(listview.model.get(indicies[i], listview.model.RoleModelData).filename));
76 }
77
78- trackQueue.appendList(items)
79+ player.mediaPlayer.playlist.addItems(items);
80
81 listview.closeSelection()
82 }
83
84=== modified file 'app/components/Helpers/ContentHubHelper.qml'
85--- app/components/Helpers/ContentHubHelper.qml 2015-10-23 03:08:43 +0000
86+++ app/components/Helpers/ContentHubHelper.qml 2016-01-26 00:01:55 +0000
87@@ -1,5 +1,5 @@
88 /*
89- * Copyright (C) 2014, 2015
90+ * Copyright (C) 2014, 2015, 2016
91 * Andrew Hayzen <ahayzen@gmail.com>
92 * Daniel Holm <d.holmen@gmail.com>
93 * Victor Thompson <victor.thompson@gmail.com>
94@@ -154,9 +154,6 @@
95 contentHubWaitForFile.searchPaths = contentHub.searchPaths;
96 contentHubWaitForFile.processId = processId;
97 contentHubWaitForFile.start();
98-
99- // Stop queue loading in bg
100- queueLoaderWorker.canLoad = false
101 } else {
102 contentHubWaitForFile.searchPaths.push.apply(contentHubWaitForFile.searchPaths, contentHub.searchPaths);
103 contentHubWaitForFile.count = 0;
104@@ -220,15 +217,21 @@
105 else {
106 stopTimer();
107
108- trackQueue.clear();
109+ player.mediaPlayer.playlist.clearWrapper();
110+
111+ var items = [];
112
113 for (i=0; i < searchPaths.length; i++) {
114- model = musicStore.lookup(decodeFileURI(searchPaths[i]))
115-
116- trackQueue.append(makeDict(model));
117+ // Don't need to check if in ms2 as that is done above
118+ items.push(Qt.resolvedUrl(decodeURIComponent(searchPaths[i])))
119 }
120
121+ player.mediaPlayer.playlist.addItems(items);
122+
123 trackQueueClick(0);
124+
125+ // Show the Now playing page and make sure the track is visible
126+ tabs.pushNowPlaying();
127 }
128 }
129 }
130
131=== modified file 'app/components/Helpers/UriHandlerHelper.qml'
132--- app/components/Helpers/UriHandlerHelper.qml 2015-08-12 23:36:44 +0000
133+++ app/components/Helpers/UriHandlerHelper.qml 2016-01-26 00:01:55 +0000
134@@ -1,5 +1,5 @@
135 /*
136- * Copyright (C) 2013, 2014, 2015
137+ * Copyright (C) 2013, 2014, 2015, 2016
138 * Andrew Hayzen <ahayzen@gmail.com>
139 * Daniel Holm <d.holmen@gmail.com>
140 * Victor Thompson <victor.thompson@gmail.com>
141@@ -38,14 +38,6 @@
142 }
143
144 function processAlbum(uri) {
145- // Stop queue loading in the background
146- queueLoaderWorker.canLoad = false
147-
148- if (queueLoaderWorker.processing > 0) {
149- waitForWorker.workerStop(queueLoaderWorker, processAlbum, [uri])
150- return;
151- }
152-
153 selectedAlbum = true;
154 var split = uri.split("/");
155
156@@ -60,34 +52,27 @@
157 }
158
159 function processFile(uri, play) {
160- // Stop queue loading in the background
161- queueLoaderWorker.canLoad = false
162-
163- if (queueLoaderWorker.processing > 0) {
164- waitForWorker.workerStop(queueLoaderWorker, processFile, [uri, play])
165- return;
166- }
167-
168 // Lookup track in songs model
169 var track = musicStore.lookup(decodeFileURI(uri));
170
171 if (!track) {
172 console.debug("Unknown file " + uri + ", skipping")
173- return;
174- }
175-
176- if (play) {
177- // clear play queue
178- trackQueue.clear()
179- }
180-
181- // enqueue
182- trackQueue.append(makeDict(track));
183-
184- // play first URI
185- if (play) {
186- trackQueueClick(trackQueue.model.count - 1);
187- }
188+ } else {
189+ if (play) {
190+ // clear play queue
191+ player.mediaPlayer.playlist.clearWrapper()
192+ }
193+
194+ // enqueue
195+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(track.filename));
196+
197+ // play first URI
198+ if (play) {
199+ trackQueueClick(player.mediaPlayer.playlist.itemCount - 1);
200+ tabs.pushNowPlaying(); // ensure now playing is shown for first
201+ }
202+ }
203+
204 }
205
206 function process(uri, play) {
207
208=== modified file 'app/components/Helpers/UserMetricsHelper.qml'
209--- app/components/Helpers/UserMetricsHelper.qml 2015-08-12 23:36:44 +0000
210+++ app/components/Helpers/UserMetricsHelper.qml 2016-01-26 00:01:55 +0000
211@@ -1,5 +1,5 @@
212 /*
213- * Copyright (C) 2013, 2014, 2015
214+ * Copyright (C) 2013, 2014, 2015, 2016
215 * Andrew Hayzen <ahayzen@gmail.com>
216 * Daniel Holm <d.holmen@gmail.com>
217 * Victor Thompson <victor.thompson@gmail.com>
218@@ -36,21 +36,24 @@
219 // Connections for usermetrics
220 Connections {
221 id: userMetricPlayerConnection
222- target: player
223+ target: player.mediaPlayer
224+
225 property bool songCounted: false
226
227- onSourceChanged: {
228- songCounted = false
229- }
230-
231 onPositionChanged: {
232 // Increment song count on Welcome screen if song has been
233 // playing for over 10 seconds.
234- if (player.position > 10000 && !songCounted) {
235+ if (player.mediaPlayer.position > 10000 && !songCounted) {
236 songCounted = true
237 songsMetric.increment()
238 console.debug("Increment UserMetrics")
239 }
240 }
241 }
242+
243+ Connections {
244+ target: player.mediaPlayer.playlist
245+ onCurrentIndexChanged: userMetricPlayerConnection.songCounted = false
246+ onCurrentItemSourceChanged: userMetricPlayerConnection.songCounted = false
247+ }
248 }
249
250=== modified file 'app/components/ListItemActions/AddToPlaylist.qml'
251--- app/components/ListItemActions/AddToPlaylist.qml 2015-10-18 18:16:05 +0000
252+++ app/components/ListItemActions/AddToPlaylist.qml 2016-01-26 00:01:55 +0000
253@@ -1,5 +1,5 @@
254 /*
255- * Copyright (C) 2014, 2015
256+ * Copyright (C) 2014, 2015, 2016
257 * Andrew Hayzen <ahayzen@gmail.com>
258 * Daniel Holm <d.holmen@gmail.com>
259 * Victor Thompson <victor.thompson@gmail.com>
260@@ -25,10 +25,14 @@
261 objectName: "addToPlaylistAction"
262 text: i18n.tr("Add to playlist")
263
264+ // Used when model can't be given to add to playlist
265+ // for example in the Queue it is called metaModel not model
266+ property var modelOverride: null
267+
268 onTriggered: {
269 console.debug("Debug: Add track to playlist");
270
271 mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
272- {"chosenElements": [makeDict(model)]})
273+ {"chosenElements": [modelOverride || makeDict(model)]})
274 }
275 }
276
277=== modified file 'app/components/ListItemActions/AddToQueue.qml'
278--- app/components/ListItemActions/AddToQueue.qml 2015-08-12 23:36:44 +0000
279+++ app/components/ListItemActions/AddToQueue.qml 2016-01-26 00:01:55 +0000
280@@ -1,5 +1,5 @@
281 /*
282- * Copyright (C) 2014, 2015
283+ * Copyright (C) 2014, 2015, 2016
284 * Andrew Hayzen <ahayzen@gmail.com>
285 * Daniel Holm <d.holmen@gmail.com>
286 * Victor Thompson <victor.thompson@gmail.com>
287@@ -29,6 +29,6 @@
288
289 onTriggered: {
290 console.debug("Debug: Add track to queue: " + model)
291- trackQueue.append(model)
292+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(model.filename))
293 }
294 }
295
296=== modified file 'app/components/MusicToolbar.qml'
297--- app/components/MusicToolbar.qml 2015-08-12 23:36:44 +0000
298+++ app/components/MusicToolbar.qml 2016-01-26 00:01:55 +0000
299@@ -1,5 +1,5 @@
300 /*
301- * Copyright (C) 2013, 2014, 2015
302+ * Copyright (C) 2013, 2014, 2015, 2016
303 * Andrew Hayzen <ahayzen@gmail.com>
304 * Daniel Holm <d.holmen@gmail.com>
305 * Victor Thompson <victor.thompson@gmail.com>
306@@ -18,7 +18,7 @@
307 */
308
309 import QtQuick 2.4
310-import QtMultimedia 5.0
311+import QtMultimedia 5.6
312 import Ubuntu.Components 1.3
313
314 Rectangle {
315@@ -42,7 +42,7 @@
316 anchors {
317 fill: parent
318 }
319- state: trackQueue.model.count === 0 ? "disabled" : "enabled"
320+ state: player.mediaPlayer.playlist.empty ? "disabled" : "enabled"
321 states: [
322 State {
323 name: "disabled"
324@@ -104,7 +104,7 @@
325 }
326 color: "#FFF"
327 height: units.gu(4)
328- name: player.playbackState === MediaPlayer.PlayingState ?
329+ name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
330 "media-playback-pause" : "media-playback-start"
331 objectName: "disabledSmallPlayShape"
332 width: height
333@@ -116,11 +116,10 @@
334 fill: parent
335 }
336 onClicked: {
337- if (trackQueue.model.count === 0) {
338+ if (player.mediaPlayer.playlist.empty) {
339 playRandomSong();
340- }
341- else {
342- player.toggle();
343+ } else {
344+ player.mediaPlayer.toggle();
345 }
346 }
347 }
348@@ -144,7 +143,7 @@
349 left: parent.left
350 top: parent.top
351 }
352- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
353+ covers: [player.currentMeta]
354 size: parent.height
355 }
356
357@@ -170,8 +169,9 @@
358 elide: Text.ElideRight
359 fontSize: "small"
360 font.weight: Font.DemiBold
361- text: player.currentMetaTitle === ""
362- ? player.source : player.currentMetaTitle
363+ text: player.currentMeta.title === ""
364+ ? player.mediaPlayer.playlist.currentItemSource
365+ : player.currentMeta.title
366 }
367
368 /* Artist of track */
369@@ -185,7 +185,7 @@
370 elide: Text.ElideRight
371 fontSize: "small"
372 opacity: 0.4
373- text: player.currentMetaArtist
374+ text: player.currentMeta.author
375 }
376 }
377
378@@ -197,9 +197,9 @@
379 rightMargin: units.gu(3)
380 verticalCenter: parent.verticalCenter
381 }
382- color: "#FFF"
383+ color: playerControlsPlayButtonMouseArea.pressed ? UbuntuColors.blue : "white"
384 height: units.gu(4)
385- name: player.playbackState === MediaPlayer.PlayingState ?
386+ name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
387 "media-playback-pause" : "media-playback-start"
388 objectName: "playShape"
389 width: height
390@@ -217,12 +217,13 @@
391
392 /* Mouse area for the play button (ontop of the jump to now playing) */
393 MouseArea {
394+ id: playerControlsPlayButtonMouseArea
395 anchors {
396 bottom: parent.bottom
397 horizontalCenter: playerControlsPlayButton.horizontalCenter
398 top: parent.top
399 }
400- onClicked: player.toggle()
401+ onClicked: player.mediaPlayer.toggle()
402 width: units.gu(8)
403
404 Rectangle {
405@@ -260,16 +261,29 @@
406 }
407 color: UbuntuColors.blue
408 height: parent.height
409- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
410+ width: player.mediaPlayer.progress * playerControlsProgressBar.width
411+
412+ // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
413+ // emit until it changes to the PLAYING state. But by asking for a
414+ // value we get gst to perform a query and return a result
415+ // However this has to be done once the source is set, hence the delay
416+ //
417+ // NOTE: This does not solve when the currentIndex is removed though
418+ Timer {
419+ id: refreshProgressTimer
420+ interval: 48
421+ repeat: false
422+
423+ // Use binding so the width updates and value isn't saved
424+ onTriggered: playerControlsProgressBarHint.width = Qt.binding(function() { return player.mediaPlayer.progress * playerControlsProgressBar.width; })
425+ }
426
427 Connections {
428- target: player
429- onPositionChanged: {
430- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
431- }
432- onStopped: {
433- playerControlsProgressBarHint.width = 0;
434- }
435+ target: player.mediaPlayer.playlist
436+ // Call timer when source or index changes
437+ // so we call even if there are duplicate sources or source removal
438+ onCurrentItemSourceChanged: refreshProgressTimer.start()
439+ onCurrentIndexChanged: refreshProgressTimer.start()
440 }
441 }
442 }
443
444=== modified file 'app/components/NowPlayingFullView.qml'
445--- app/components/NowPlayingFullView.qml 2015-10-18 17:45:48 +0000
446+++ app/components/NowPlayingFullView.qml 2016-01-26 00:01:55 +0000
447@@ -1,5 +1,5 @@
448 /*
449- * Copyright (C) 2013, 2014, 2015
450+ * Copyright (C) 2013, 2014, 2015, 2016
451 * Andrew Hayzen <ahayzen@gmail.com>
452 * Daniel Holm <d.holmen@gmail.com>
453 * Victor Thompson <victor.thompson@gmail.com>
454@@ -51,7 +51,7 @@
455 CoverGrid {
456 id: albumImage
457 anchors.centerIn: parent
458- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
459+ covers: [player.currentMeta]
460 size: parent.height
461 }
462 }
463@@ -92,7 +92,15 @@
464 fontSize: "x-large"
465 maximumLineCount: 2
466 objectName: "playercontroltitle"
467- text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
468+ text: {
469+ if (player.mediaPlayer.playlist.empty) {
470+ ""
471+ } else if (player.currentMeta.title === "") {
472+ player.mediaPlayer.playlist.currentSource
473+ } else {
474+ player.currentMeta.title
475+ }
476+ }
477 wrapMode: Text.WordWrap
478 }
479
480@@ -108,7 +116,7 @@
481 color: styleMusic.nowPlaying.labelSecondaryColor
482 elide: Text.ElideRight
483 fontSize: "small"
484- text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
485+ text: player.mediaPlayer.playlist.empty ? "" : player.currentMeta.author
486 }
487 }
488
489@@ -122,12 +130,13 @@
490
491 onReleased: {
492 var diff = mouse.x - lastX
493+
494 if (Math.abs(diff) < units.gu(4)) {
495 return;
496 } else if (diff < 0) {
497- player.nextSong()
498+ player.mediaPlayer.playlist.nextWrapper()
499 } else if (diff > 0) {
500- player.previousSong()
501+ player.mediaPlayer.playlist.previousWrapper()
502 }
503 }
504 }
505@@ -167,7 +176,7 @@
506 fontSize: "small"
507 height: parent.height
508 horizontalAlignment: Text.AlignHCenter
509- text: durationToString(player.position)
510+ text: durationToString(player.mediaPlayer.position)
511 verticalAlignment: Text.AlignVCenter
512 width: units.gu(3)
513 }
514@@ -176,10 +185,10 @@
515 id: progressSliderMusic
516 anchors.left: parent.left
517 anchors.right: parent.right
518- maximumValue: player.duration // load value at startup
519+ maximumValue: player.mediaPlayer.duration || 1 // fallback to 1 when 0 so that the progress bar works
520 objectName: "progressSliderShape"
521 style: UbuntuBlueSliderStyle {}
522- value: player.position // load value at startup
523+ value: player.mediaPlayer.position // load value at startup
524
525 function formatValue(v) {
526 if (seeking) { // update position label while dragging
527@@ -194,7 +203,7 @@
528
529 onSeekingChanged: {
530 if (seeking === false) {
531- musicToolbarFullPositionLabel.text = durationToString(player.position)
532+ musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
533 }
534 }
535
536@@ -203,22 +212,23 @@
537
538 if (!pressed) {
539 seeked = true
540- player.seek(value)
541+ player.mediaPlayer.seek(value)
542
543 musicToolbarFullPositionLabel.text = durationToString(value)
544 }
545 }
546
547 Connections {
548- target: player
549+ target: player.mediaPlayer
550 onPositionChanged: {
551 // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
552 if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
553- musicToolbarFullPositionLabel.text = durationToString(player.position)
554- musicToolbarFullDurationLabel.text = durationToString(player.duration)
555+ musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
556+ musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration)
557
558- progressSliderMusic.value = player.position
559- progressSliderMusic.maximumValue = player.duration
560+ progressSliderMusic.value = player.mediaPlayer.position
561+ // fallback to 1 when 0 so that the progress bar works
562+ progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
563 }
564
565 progressSliderMusic.seeked = false;
566@@ -240,9 +250,39 @@
567 fontSize: "small"
568 height: parent.height
569 horizontalAlignment: Text.AlignHCenter
570- text: durationToString(player.duration)
571+ text: durationToString(player.mediaPlayer.duration || 1)
572 verticalAlignment: Text.AlignVCenter
573 width: units.gu(3)
574 }
575+
576+ // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
577+ // emit until it changes to the PLAYING state. But by asking for a
578+ // value we get gst to perform a query and return a result
579+ // However this has to be done once the source is set, hence the delay
580+ //
581+ // NOTE: This does not solve when the currentIndex is removed though
582+ Timer {
583+ id: refreshProgressTimer
584+ interval: 48
585+ repeat: false
586+ onTriggered: {
587+ if (!progressSliderMusic.seeking) {
588+ musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position);
589+ musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration || 1);
590+
591+ progressSliderMusic.value = player.mediaPlayer.position
592+ // fallback to 1 when 0 so that the progress bar works
593+ progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
594+ }
595+ }
596+ }
597+
598+ Connections {
599+ target: player.mediaPlayer.playlist
600+ // Call timer when source or index changes
601+ // so we call even if there are duplicate sources or source removal
602+ onCurrentItemSourceChanged: refreshProgressTimer.start()
603+ onCurrentIndexChanged: refreshProgressTimer.start()
604+ }
605 }
606 }
607
608=== modified file 'app/components/NowPlayingToolbar.qml'
609--- app/components/NowPlayingToolbar.qml 2015-08-12 23:36:44 +0000
610+++ app/components/NowPlayingToolbar.qml 2016-01-26 00:01:55 +0000
611@@ -1,5 +1,5 @@
612 /*
613- * Copyright (C) 2013, 2014, 2015
614+ * Copyright (C) 2013, 2014, 2015, 2016
615 * Andrew Hayzen <ahayzen@gmail.com>
616 * Daniel Holm <d.holmen@gmail.com>
617 * Victor Thompson <victor.thompson@gmail.com>
618@@ -17,7 +17,7 @@
619 * along with this program. If not, see <http://www.gnu.org/licenses/>.
620 */
621
622-import QtMultimedia 5.0
623+import QtMultimedia 5.6
624 import QtQuick 2.4
625 import Ubuntu.Components 1.3
626
627@@ -37,7 +37,6 @@
628 anchors.rightMargin: units.gu(1)
629 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
630 height: units.gu(6)
631- opacity: player.repeat ? 1 : .4
632 width: height
633 onClicked: player.repeat = !player.repeat
634
635@@ -47,23 +46,23 @@
636 width: height
637 anchors.verticalCenter: parent.verticalCenter
638 anchors.horizontalCenter: parent.horizontalCenter
639- color: "white"
640+ color: parent.pressed ? UbuntuColors.blue : "white"
641 name: "media-playlist-repeat"
642 objectName: "repeatShape"
643- opacity: player.repeat ? 1 : .4
644+ opacity: player.repeat ? 1 : .2
645 }
646 }
647
648 /* Previous button */
649 MouseArea {
650 id: nowPlayingPreviousButton
651+ enabled: player.mediaPlayer.playlist.canGoPrevious
652 anchors.right: nowPlayingPlayButton.left
653 anchors.rightMargin: units.gu(1)
654 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
655 height: units.gu(6)
656- opacity: trackQueue.model.count === 0 ? .4 : 1
657 width: height
658- onClicked: player.previousSong()
659+ onClicked: player.mediaPlayer.playlist.previousWrapper()
660
661 Icon {
662 id: nowPlayingPreviousIndicator
663@@ -71,10 +70,10 @@
664 width: height
665 anchors.verticalCenter: parent.verticalCenter
666 anchors.horizontalCenter: parent.horizontalCenter
667- color: "white"
668+ color: parent.pressed ? UbuntuColors.blue : "white"
669 name: "media-skip-backward"
670 objectName: "previousShape"
671- opacity: 1
672+ opacity: parent.enabled ? 1 : .2
673 }
674 }
675
676@@ -84,7 +83,7 @@
677 anchors.centerIn: parent
678 height: units.gu(10)
679 width: height
680- onClicked: player.toggle()
681+ onClicked: player.mediaPlayer.toggle()
682
683 Icon {
684 id: nowPlayingPlayIndicator
685@@ -92,8 +91,8 @@
686 width: height
687 anchors.verticalCenter: parent.verticalCenter
688 anchors.horizontalCenter: parent.horizontalCenter
689- color: "white"
690- name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
691+ color: parent.pressed ? UbuntuColors.blue : "white"
692+ name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
693 objectName: "playShape"
694 }
695 }
696@@ -104,10 +103,10 @@
697 anchors.left: nowPlayingPlayButton.right
698 anchors.leftMargin: units.gu(1)
699 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
700+ enabled: player.mediaPlayer.playlist.canGoNext
701 height: units.gu(6)
702- opacity: trackQueue.model.count === 0 ? .4 : 1
703 width: height
704- onClicked: player.nextSong()
705+ onClicked: player.mediaPlayer.playlist.nextWrapper()
706
707 Icon {
708 id: nowPlayingNextIndicator
709@@ -115,10 +114,10 @@
710 width: height
711 anchors.verticalCenter: parent.verticalCenter
712 anchors.horizontalCenter: parent.horizontalCenter
713- color: "white"
714+ color: parent.pressed ? UbuntuColors.blue : "white"
715 name: "media-skip-forward"
716 objectName: "forwardShape"
717- opacity: 1
718+ opacity: parent.enabled ? 1 : .2
719 }
720 }
721
722@@ -129,7 +128,6 @@
723 anchors.leftMargin: units.gu(1)
724 anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
725 height: units.gu(6)
726- opacity: player.shuffle ? 1 : .4
727 width: height
728 onClicked: player.shuffle = !player.shuffle
729
730@@ -139,10 +137,10 @@
731 width: height
732 anchors.verticalCenter: parent.verticalCenter
733 anchors.horizontalCenter: parent.horizontalCenter
734- color: "white"
735+ color: parent.pressed ? UbuntuColors.blue : "white"
736 name: "media-playlist-shuffle"
737 objectName: "shuffleShape"
738- opacity: player.shuffle ? 1 : .4
739+ opacity: player.shuffle ? 1 : .2
740 }
741 }
742
743@@ -166,17 +164,7 @@
744 }
745 color: UbuntuColors.blue
746 height: parent.height
747- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
748-
749- Connections {
750- target: player
751- onPositionChanged: {
752- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
753- }
754- onStopped: {
755- playerControlsProgressBarHint.width = 0;
756- }
757- }
758+ width: player.mediaPlayer.progress * playerControlsProgressBar.width
759 }
760 }
761 }
762
763=== added file 'app/components/Player.qml'
764--- app/components/Player.qml 1970-01-01 00:00:00 +0000
765+++ app/components/Player.qml 2016-01-26 00:01:55 +0000
766@@ -0,0 +1,360 @@
767+/*
768+ * Copyright (C) 2015, 2016
769+ * Andrew Hayzen <ahayzen@gmail.com>
770+ * Victor Thompson <victor.thompson@gmail.com>
771+ *
772+ * This program is free software; you can redistribute it and/or modify
773+ * it under the terms of the GNU General Public License as published by
774+ * the Free Software Foundation; version 3.
775+ *
776+ * This program is distributed in the hope that it will be useful,
777+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
778+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
779+ * GNU General Public License for more details.
780+ *
781+ * You should have received a copy of the GNU General Public License
782+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
783+ */
784+
785+import QtMultimedia 5.6
786+import QtQuick 2.4
787+import Qt.labs.settings 1.0
788+
789+import QtQuick.LocalStorage 2.0
790+import "../logic/meta-database.js" as Library
791+
792+Item {
793+ objectName: "player"
794+
795+ // For autopilot as we can't access the MediaPlayer object pad.lv/1269578
796+ readonly property bool isPlaying: mediaPlayerObject.playbackState === MediaPlayer.PlayingState
797+ readonly property alias count: mediaPlayerPlaylist.itemCount
798+ readonly property alias currentIndex: mediaPlayerPlaylist.currentIndex
799+ readonly property alias currentItemSource: mediaPlayerPlaylist.currentItemSource
800+ readonly property alias position: mediaPlayerObject.position
801+
802+ // FIXME: pad.lv/1269578 use Item as autopilot cannot 'see' a var/QtObject
803+ property alias currentMeta: currentMetaItem
804+
805+ property alias mediaPlayer: mediaPlayerObject
806+ property alias repeat: settings.repeat
807+ property alias shuffle: settings.shuffle
808+
809+ Item {
810+ id: currentMetaItem
811+ objectName: "currentMeta"
812+
813+ property string album: ""
814+ property string art: ""
815+ property string author: ""
816+ property string filename: ""
817+ property string title: ""
818+ }
819+
820+ // Return the metadata for the source given from mediascanner2
821+ function metaForSource(source) {
822+ var blankMeta = {
823+ album: "",
824+ art: "",
825+ author: "",
826+ filename: "",
827+ title: ""
828+ };
829+
830+ source = source.toString();
831+
832+ if (source.indexOf("file://") === 0) {
833+ source = source.substring(7);
834+ }
835+
836+ return musicStore.lookup(decodeFileURI(source)) || blankMeta;
837+ }
838+
839+ Settings {
840+ id: settings
841+ category: "PlayerSettings"
842+
843+ property bool repeat: true
844+ property bool shuffle: false
845+ }
846+
847+ MediaPlayer {
848+ id: mediaPlayerObject
849+ playlist: Playlist {
850+ id: mediaPlayerPlaylist
851+ playbackMode: {
852+ if (settings.shuffle) {
853+ Playlist.Random
854+ } else if (settings.repeat) {
855+ Playlist.Loop
856+ } else {
857+ Playlist.Sequential
858+ }
859+ }
860+
861+ // as that doesn't emit changes
862+ readonly property bool canGoPrevious: { // FIXME: pad.lv/1517580 use previousIndex() > -1 after mh implements it
863+ currentIndex !== 0 ||
864+ settings.repeat ||
865+ settings.shuffle || // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
866+ mediaPlayerObject.position > 5000
867+ }
868+ readonly property bool canGoNext: { // FIXME: pad.lv/1517580 use nextIndex() > -1 after mh implements it
869+ currentIndex !== (itemCount - 1) ||
870+ settings.repeat ||
871+ settings.shuffle // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
872+ }
873+ readonly property int count: itemCount // header actions etc depend on the model having 'count'
874+ readonly property bool empty: itemCount === 0
875+ property int pendingCurrentIndex: -1
876+ property var pendingCurrentState: null
877+ property int pendingShuffle: -1
878+
879+ onCurrentItemSourceChanged: {
880+ var meta = metaForSource(currentItemSource);
881+
882+ currentMeta.album = meta.album;
883+ currentMeta.art = meta.art;
884+ currentMeta.author = meta.author;
885+ currentMeta.filename = meta.filename;
886+ currentMeta.title = meta.title;
887+
888+ mediaPlayerObject._calcProgress();
889+ }
890+ onItemChanged: {
891+ console.debug("*** Saving play queue in onItemChanged");
892+ saveQueue()
893+ }
894+ onItemInserted: {
895+ // When add to queue is done on an empty list currentIndex needs to be set
896+ if (start === 0 && currentIndex === -1 && pendingCurrentIndex < 1 && pendingShuffle === -1) {
897+ currentIndex = 0;
898+
899+ pendingCurrentIndex = -1;
900+ processPendingCurrentState();
901+ }
902+
903+ // Check if the pendingCurrentIndex is now valid
904+ if (pendingCurrentIndex !== -1 && pendingCurrentIndex < itemCount) {
905+ currentIndex = pendingCurrentIndex;
906+
907+ pendingCurrentIndex = -1;
908+ processPendingCurrentState();
909+ }
910+
911+ // Check if there is pending shuffle
912+ // pendingShuffle holds the expected size of the model
913+ if (pendingShuffle > -1 && pendingShuffle <= itemCount) {
914+ pendingShuffle = -1;
915+
916+ nextWrapper(); // find a random track
917+ mediaPlayerObject.play(); // next does not enforce play
918+ }
919+
920+ console.debug("*** Saving play queue in onItemInserted");
921+ saveQueue()
922+ }
923+ onItemRemoved: {
924+ console.debug("*** Saving play queue in onItemRemoved");
925+ saveQueue()
926+ }
927+
928+ function addItemsFromModel(model) {
929+ var items = []
930+
931+ // TODO: remove once playlists uses U1DB
932+ if (model.hasOwnProperty("linkLibraryListModel")) {
933+ model = model.linkLibraryListModel;
934+ }
935+
936+ for (var i=0; i < model.rowCount; i++) {
937+ items.push(Qt.resolvedUrl(model.get(i, model.RoleModelData).filename));
938+ }
939+
940+ addItems(items);
941+ }
942+
943+ // Wrap the clear() method because we need to call stop first
944+ function clearWrapper() {
945+ // Stop the current playback (this ensures that play is run later)
946+ if (mediaPlayerObject.playbackState === MediaPlayer.PlayingState) {
947+ mediaPlayerObject.stop();
948+ }
949+
950+ clear();
951+ }
952+
953+ // Replicates a model.get() on a ms2 model
954+ function get(index, role) {
955+ return metaForSource(itemSource(index));
956+ }
957+
958+ // Wrap the next() method so we can check canGoNext
959+ function nextWrapper() {
960+ if (canGoNext) {
961+ next();
962+ }
963+ }
964+
965+ // Wrap the previous() method so we can check canGoPrevious
966+ function previousWrapper() {
967+ if (canGoPrevious) {
968+ previous();
969+ }
970+ }
971+
972+ // Process the pending current PlaybackState
973+ function processPendingCurrentState() {
974+ if (pendingCurrentState === MediaPlayer.PlayingState) {
975+ console.debug("Loading pending state play()");
976+ mediaPlayerObject.play();
977+ } else if (pendingCurrentState === MediaPlayer.PausedState) {
978+ console.debug("Loading pending state pause()");
979+ mediaPlayerObject.pause();
980+ } else if (pendingCurrentState === MediaPlayer.StoppedState) {
981+ console.debug("Loading pending state stop()");
982+ mediaPlayerObject.stop();
983+ }
984+
985+ pendingCurrentState = null;
986+ }
987+
988+ // Wrapper for removeItems(from, to) so that we can use removeItems(list) until it is implemented upstream
989+ function removeItemsWrapper(items) {
990+ var previous = -1, end = -1;
991+
992+ // Sort indexes backwards so we don't have to deal with offsets when removing
993+ items.sort(function(a,b) { return b-a; });
994+
995+ console.debug("To Remove", JSON.stringify(items));
996+
997+ // Merge ranges of indexes into sets of start, end points
998+ // and call removeItems as we go along
999+ for (var i=0; i < items.length; i++) {
1000+ if (end == -1) { // first value found set to first
1001+ end = items[i];
1002+ } else if (previous - 1 !== items[i]) { // set has ended (next is not 1 lower)
1003+ console.debug("RemoveItems", previous, end);
1004+ player.mediaPlayer.playlist.removeItems(previous, end);
1005+
1006+ end = items[i]; // set new high value for the next set
1007+ }
1008+
1009+ previous = items[i]; // last value to check if next is 1 lower
1010+ }
1011+
1012+ // Remove last set in list as well
1013+ if (items.length > 0) {
1014+ console.debug("RemoveItems", items[items.length - 1], end);
1015+ player.mediaPlayer.playlist.removeItems(items[items.length - 1], end);
1016+ }
1017+ }
1018+
1019+ function saveQueue(start, end) {
1020+ // FIXME: load and save do not work yet pad.lv/1510225
1021+ // so use our localstorage method for now
1022+ // save("/home/phablet/.local/share/com.ubuntu.music/queue.m3u");
1023+ if (mainView.loadedUI) {
1024+ // Don't be intelligent, just clear and rebuild the queue for now
1025+ Library.clearQueue();
1026+
1027+ var sources = [];
1028+
1029+ for (var i=0; i < mediaPlayerPlaylist.itemCount; i++) {
1030+ sources.push(mediaPlayerPlaylist.itemSource(i));
1031+ }
1032+
1033+ if (sources.length > 0) {
1034+ Library.addQueueList(sources);
1035+ }
1036+ }
1037+ }
1038+
1039+ function setCurrentIndex(index) {
1040+ // Set the currentIndex but if the itemCount is too low then wait
1041+ if (index < mediaPlayerPlaylist.itemCount) {
1042+ mediaPlayerPlaylist.currentIndex = index;
1043+ } else {
1044+ pendingCurrentIndex = index;
1045+ }
1046+ }
1047+
1048+ function setPendingCurrentState(pendingState) {
1049+ // Set the PlaybackState to set once pendingCurrentIndex is set
1050+ pendingCurrentState = pendingState;
1051+
1052+ if (pendingCurrentIndex === -1) {
1053+ processPendingCurrentState();
1054+ }
1055+ }
1056+
1057+ function setPendingShuffle(modelSize) {
1058+ // Run next() and play() when the modelSize is reached
1059+ if (modelSize <= itemCount) {
1060+ mediaPlayerPlaylist.nextWrapper(); // find a random track
1061+ mediaPlayerObject.play(); // next does not enforce play
1062+ } else {
1063+ pendingShuffle = modelSize;
1064+ }
1065+ }
1066+ }
1067+
1068+ property bool endOfMedia: false
1069+ property double progress: 0
1070+
1071+ onDurationChanged: _calcProgress()
1072+ onPositionChanged: _calcProgress()
1073+
1074+ onStatusChanged: {
1075+ if (status == MediaPlayer.EndOfMedia && !settings.repeat) {
1076+ console.debug("End of media, stopping.")
1077+
1078+ // Tells the onStopped to set the curentIndex = 0
1079+ endOfMedia = true;
1080+
1081+ stop();
1082+ }
1083+ }
1084+
1085+ onStopped: { // hit when pressing next() on last track with repeat off
1086+ console.debug("onStopped.")
1087+
1088+ // FIXME: Workaround for pad.lv/1494031 in the stopped state
1089+ // we do not get position/duration info so if there are items in
1090+ // the queue and we have stopped instead pause
1091+ if (playlist.itemCount > 0) {
1092+ // We have just ended media so jump to start of playlist
1093+ if (endOfMedia) {
1094+ playlist.currentIndex = 0;
1095+
1096+ // Play then pause otherwise when we come from EndOfMedia
1097+ // if calls next() until EndOfMedia again
1098+ play();
1099+ }
1100+
1101+ pause();
1102+ }
1103+
1104+ endOfMedia = false; // always reset endOfMedia
1105+ _calcProgress(); // ensures progress bar has reset
1106+ }
1107+
1108+ function _calcProgress() {
1109+ if (duration > 0) {
1110+ progress = position / duration;
1111+ } else if (position >= duration) {
1112+ progress = 0;
1113+ } else {
1114+ progress = 0;
1115+ }
1116+ }
1117+
1118+ function toggle() {
1119+ if (playbackState === MediaPlayer.PlayingState) {
1120+ pause();
1121+ } else {
1122+ play();
1123+ }
1124+ }
1125+ }
1126+}
1127
1128=== removed file 'app/components/Player.qml'
1129--- app/components/Player.qml 2015-06-28 03:06:49 +0000
1130+++ app/components/Player.qml 1970-01-01 00:00:00 +0000
1131@@ -1,240 +0,0 @@
1132-/*
1133- * Copyright (C) 2013, 2014, 2015
1134- * Andrew Hayzen <ahayzen@gmail.com>
1135- * Daniel Holm <d.holmen@gmail.com>
1136- * Victor Thompson <victor.thompson@gmail.com>
1137- *
1138- * This program is free software; you can redistribute it and/or modify
1139- * it under the terms of the GNU General Public License as published by
1140- * the Free Software Foundation; version 3.
1141- *
1142- * This program is distributed in the hope that it will be useful,
1143- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1144- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1145- * GNU General Public License for more details.
1146- *
1147- * You should have received a copy of the GNU General Public License
1148- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1149- */
1150-
1151-import QtQuick 2.4
1152-import QtMultimedia 5.0
1153-import QtQuick.LocalStorage 2.0
1154-import Qt.labs.settings 1.0
1155-
1156-/*
1157- * This file should *only* manage the media playing and the relevant settings
1158- * It should therefore only access MediaPlayer, trackQueue and Settings
1159- * Anything else within the app should use Connections to listen for changes
1160- */
1161-
1162-
1163-Item {
1164- objectName: "player"
1165-
1166- property string currentMetaAlbum: ""
1167- property string currentMetaArt: ""
1168- property string currentMetaArtist: ""
1169- property string currentMetaFile: ""
1170- property string currentMetaTitle: ""
1171- property int currentIndex: -1
1172- property int duration: 1
1173- readonly property bool isPlaying: player.playbackState === MediaPlayer.PlayingState
1174- readonly property var playbackState: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.playbackState : MediaPlayer.StoppedState
1175- property int position: 0
1176- property alias repeat: settings.repeat
1177- property alias shuffle: settings.shuffle
1178- readonly property string source: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.source : ""
1179- readonly property double volume: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.volume : 1.0
1180-
1181- signal stopped()
1182-
1183- Settings {
1184- id: settings
1185- category: "PlayerSettings"
1186-
1187- property bool repeat: true
1188- property bool shuffle: false
1189- }
1190-
1191- Connections {
1192- target: trackQueue.model
1193- onCountChanged: {
1194- if (trackQueue.model.count === 1 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
1195- player.currentIndex = 0;
1196- setSource(Qt.resolvedUrl(trackQueue.model.get(0).filename))
1197- } else if (trackQueue.model.count === 0 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
1198- player.currentIndex = -1
1199- setSource("")
1200- }
1201- }
1202- }
1203-
1204- function getSong(direction, startPlaying, fromControls) {
1205- // Seek to start if threshold reached when selecting previous
1206- if (direction === -1 && (player.position / 1000) > 5)
1207- {
1208- player.seek(0); // seek to start
1209- return;
1210- }
1211-
1212- if (trackQueue.model.count == 0)
1213- {
1214- customdebug("No tracks in queue.");
1215- return;
1216- }
1217-
1218- // default fromControls and startPlaying to true
1219- fromControls = fromControls === undefined ? true : fromControls;
1220- startPlaying = startPlaying === undefined ? true : startPlaying;
1221- var newIndex;
1222-
1223- console.log("currentIndex: " + currentIndex)
1224- console.log("trackQueue.count: " + trackQueue.model.count)
1225-
1226- // Do not shuffle if repeat is off and there is only one track in the queue
1227- if (shuffle && !(trackQueue.model.count === 1 && !repeat)) {
1228- var now = new Date();
1229- var seed = now.getSeconds();
1230-
1231- // trackQueue must be above 1 otherwise an infinite loop will occur
1232- do {
1233- newIndex = (Math.floor((trackQueue.model.count)
1234- * Math.random(seed)));
1235- } while (newIndex === currentIndex && trackQueue.model.count > 1)
1236- } else {
1237- if ((currentIndex < trackQueue.model.count - 1 && direction === 1 )
1238- || (currentIndex > 0 && direction === -1)) {
1239- newIndex = currentIndex + direction
1240- } else if(direction === 1 && (repeat || fromControls)) {
1241- newIndex = 0
1242- } else if(direction === -1 && (repeat || fromControls)) {
1243- newIndex = trackQueue.model.count - 1
1244- }
1245- else
1246- {
1247- player.stop()
1248- return;
1249- }
1250- }
1251-
1252- if (startPlaying) { // only start the track if told
1253- playSong(trackQueue.model.get(newIndex).filename, newIndex)
1254- }
1255- else {
1256- currentIndex = newIndex
1257- setSource(Qt.resolvedUrl(trackQueue.model.get(newIndex).filename))
1258- }
1259-
1260- // Set index into queue
1261- queueIndex = currentIndex
1262- }
1263-
1264- function nextSong(startPlaying, fromControls) {
1265- getSong(1, startPlaying, fromControls)
1266- }
1267-
1268- function pause() {
1269- mediaPlayerLoader.item.pause();
1270- }
1271-
1272- function play() {
1273- mediaPlayerLoader.item.play();
1274- }
1275-
1276- function playSong(filepath, index) {
1277- stop();
1278- currentIndex = index;
1279- queueIndex = index;
1280- setSource(filepath);
1281- play();
1282- }
1283-
1284- function previousSong(startPlaying) {
1285- getSong(-1, startPlaying)
1286- }
1287-
1288- function seek(position) {
1289- mediaPlayerLoader.item.seek(position);
1290- }
1291-
1292- function setSource(filepath) {
1293- mediaPlayerLoader.item.source = Qt.resolvedUrl(filepath);
1294- }
1295-
1296- function setVolume(volume) {
1297- mediaPlayerLoader.item.volume = volume
1298- }
1299-
1300- function stop() {
1301- mediaPlayerLoader.item.stop();
1302- }
1303-
1304- function toggle() {
1305- if (player.playbackState == MediaPlayer.PlayingState) {
1306- pause()
1307- }
1308- else {
1309- play()
1310- }
1311- }
1312-
1313- Loader {
1314- id: mediaPlayerLoader
1315- asynchronous: true
1316- sourceComponent: Component {
1317- MediaPlayer {
1318- muted: false
1319-
1320- onDurationChanged: player.duration = duration
1321- onPositionChanged: player.position = position
1322-
1323- onSourceChanged: {
1324- // Force invalid source to ""
1325- if (source === undefined || source === false) {
1326- source = ""
1327- return
1328- }
1329-
1330- if (source.toString() === "") {
1331- player.currentIndex = -1
1332- player.stop()
1333- }
1334- else {
1335- var obj;
1336-
1337- if (source.toString().indexOf("file://") === 0) {
1338- obj = musicStore.lookup(decodeFileURI(source.toString().substring(7)))
1339- } else {
1340- obj = musicStore.lookup(decodeFileURI(source.toString()))
1341- }
1342-
1343- // protect against null reponse from the lookup
1344- if (obj !== null) {
1345- // protect against undefined properties
1346- player.currentMetaAlbum = obj.album || "";
1347- player.currentMetaArt = obj.art || "";
1348- player.currentMetaArtist = obj.author || "";
1349- player.currentMetaFile = obj.filename || "";
1350- player.currentMetaTitle = obj.title || "";
1351- } else {
1352- console.debug("Mediascanner lookup resulted in null object", source.toString())
1353- }
1354- }
1355-
1356- console.log("Source: " + source.toString())
1357- console.log("Index: " + player.currentIndex)
1358- }
1359-
1360- onStatusChanged: {
1361- if (status == MediaPlayer.EndOfMedia) {
1362- nextSong(true, false) // next track
1363- }
1364- }
1365-
1366- onStopped: player.stopped()
1367- }
1368- }
1369- }
1370-}
1371-
1372
1373=== modified file 'app/components/Queue.qml'
1374--- app/components/Queue.qml 2016-01-09 12:49:43 +0000
1375+++ app/components/Queue.qml 2016-01-26 00:01:55 +0000
1376@@ -31,39 +31,43 @@
1377 anchors {
1378 fill: parent
1379 }
1380+ autoModelMove: false // ensures we use moveItem() not move() in onReorder
1381 footer: Item {
1382 height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
1383 }
1384- model: trackQueue.model
1385+ model: player.mediaPlayer.playlist
1386 objectName: "nowPlayingqueueList"
1387
1388 onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
1389
1390 delegate: MusicListItem {
1391 id: queueListItem
1392- color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
1393+ color: player.mediaPlayer.playlist.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
1394+ height: units.gu(7)
1395 leadingActions: ListItemActions {
1396 actions: [
1397 Remove {
1398- onTriggered: trackQueue.removeQueueList([index])
1399+ onTriggered: player.mediaPlayer.playlist.removeItem(index)
1400 }
1401 ]
1402 }
1403 multiselectable: true
1404 objectName: "nowPlayingListItem" + index
1405+ state: ""
1406 reorderable: true
1407 subtitle {
1408 objectName: "artistLabel"
1409- text: model.author
1410+ text: metaModel.author
1411 }
1412 title {
1413- color: player.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
1414+ color: player.mediaPlayer.playlist.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
1415 objectName: "titleLabel"
1416- text: model.title
1417+ text: metaModel.title
1418 }
1419 trailingActions: ListItemActions {
1420 actions: [
1421 AddToPlaylist {
1422+ modelOverride: metaModel // model is not exposed with metadata so use metaModel
1423 }
1424 ]
1425 delegate: ActionDelegate {
1426@@ -71,24 +75,18 @@
1427 }
1428 }
1429
1430+ property var metaModel: player.metaForSource(model.source)
1431+
1432 onItemClicked: {
1433- customdebug("File: " + model.filename) // debugger
1434- trackQueueClick(index); // toggle track state
1435+ customdebug("File: " + model.source) // debugger
1436+ trackQueueClick(index);
1437 }
1438 }
1439
1440+
1441 onReorder: {
1442- Library.moveQueueItem(from, to);
1443-
1444- // Maintain currentIndex with current song
1445- if (from === player.currentIndex) {
1446- player.currentIndex = to;
1447- } else if (from < player.currentIndex && to >= player.currentIndex) {
1448- player.currentIndex -= 1;
1449- } else if (from > player.currentIndex && to <= player.currentIndex) {
1450- player.currentIndex += 1;
1451- }
1452-
1453- queueIndex = player.currentIndex
1454+ console.debug("Move: ", from, to);
1455+
1456+ player.mediaPlayer.playlist.moveItem(from, to);
1457 }
1458 }
1459
1460=== modified file 'app/components/ViewButton/PlayAllButton.qml'
1461--- app/components/ViewButton/PlayAllButton.qml 2015-08-12 23:36:44 +0000
1462+++ app/components/ViewButton/PlayAllButton.qml 2016-01-26 00:01:55 +0000
1463@@ -1,5 +1,5 @@
1464 /*
1465- * Copyright (C) 2013, 2014, 2015
1466+ * Copyright (C) 2013, 2014, 2015, 2016
1467 * Andrew Hayzen <ahayzen@gmail.com>
1468 * Daniel Holm <d.holmen@gmail.com>
1469 * Victor Thompson <victor.thompson@gmail.com>
1470@@ -30,5 +30,9 @@
1471
1472 property var model
1473
1474- onClicked: trackClicked(model, 0) // play track
1475+ onClicked: {
1476+ if (model.count > 0) {
1477+ trackClicked(model, 0) // play track
1478+ }
1479+ }
1480 }
1481
1482=== modified file 'app/components/ViewButton/QueueAllButton.qml'
1483--- app/components/ViewButton/QueueAllButton.qml 2015-08-12 23:36:44 +0000
1484+++ app/components/ViewButton/QueueAllButton.qml 2016-01-26 00:01:55 +0000
1485@@ -1,5 +1,5 @@
1486 /*
1487- * Copyright (C) 2013, 2014, 2015
1488+ * Copyright (C) 2013, 2014, 2015, 2016
1489 * Andrew Hayzen <ahayzen@gmail.com>
1490 * Daniel Holm <d.holmen@gmail.com>
1491 * Victor Thompson <victor.thompson@gmail.com>
1492@@ -28,7 +28,11 @@
1493
1494 property var model
1495
1496- onClicked: addQueueFromModel(model)
1497+ onClicked: {
1498+ if (model.count > 0) {
1499+ player.mediaPlayer.playlist.addItemsFromModel(model)
1500+ }
1501+ }
1502
1503 Text {
1504 anchors {
1505
1506=== modified file 'app/components/ViewButton/ShuffleButton.qml'
1507--- app/components/ViewButton/ShuffleButton.qml 2015-08-12 23:36:44 +0000
1508+++ app/components/ViewButton/ShuffleButton.qml 2016-01-26 00:01:55 +0000
1509@@ -1,5 +1,5 @@
1510 /*
1511- * Copyright (C) 2013, 2014, 2015
1512+ * Copyright (C) 2013, 2014, 2015, 2016
1513 * Andrew Hayzen <ahayzen@gmail.com>
1514 * Daniel Holm <d.holmen@gmail.com>
1515 * Victor Thompson <victor.thompson@gmail.com>
1516@@ -28,7 +28,11 @@
1517
1518 property var model
1519
1520- onClicked: shuffleModel(model)
1521+ onClicked: {
1522+ if (model.count > 0) {
1523+ playRandomSong(model)
1524+ }
1525+ }
1526
1527 Text {
1528 anchors {
1529
1530=== modified file 'app/logic/meta-database.js'
1531--- app/logic/meta-database.js 2015-06-20 18:01:59 +0000
1532+++ app/logic/meta-database.js 2016-01-26 00:01:55 +0000
1533@@ -1,5 +1,5 @@
1534 /*
1535- * Copyright (C) 2013, 2014, 2015
1536+ * Copyright (C) 2013, 2014, 2015, 2016
1537 * Andrew Hayzen <ahayzen@gmail.com>
1538 * Daniel Holm <d.holmen@gmail.com>
1539 * Victor Thompson <victor.thompson@gmail.com>
1540@@ -70,7 +70,7 @@
1541 var ind = getNextIndex(tx);
1542
1543 for (var i = 0; i < items.length; i++) {
1544- tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i].filename]);
1545+ tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i]]);
1546 }
1547 }
1548 );
1549@@ -156,9 +156,19 @@
1550 db.transaction( function(tx) {
1551 var rs = tx.executeSql("SELECT * FROM queue ORDER BY ind ASC");
1552 for(var i = 0; i < rs.rows.length; i++) {
1553- if (musicStore.lookup(decodeFileURI(rs.rows.item(i).filename)) != null) {
1554- res.push(makeDict(musicStore.lookup(decodeFileURI(rs.rows.item(i).filename))));
1555+ var filename = rs.rows.item(i).filename;
1556+
1557+ if (filename !== null) {
1558+ // ms2 doesn't expect the URI scheme so strip file://
1559+ if (filename.indexOf("file://") == 0) {
1560+ filename = filename.substr(7);
1561+ }
1562+
1563+ if (musicStore.lookup(decodeFileURI(filename)) != null) {
1564+ res.push(Qt.resolvedUrl(filename));
1565+ }
1566 }
1567+
1568 }
1569 });
1570 return res;
1571
1572=== modified file 'app/logic/playlists.js'
1573--- app/logic/playlists.js 2015-06-20 17:57:58 +0000
1574+++ app/logic/playlists.js 2016-01-26 00:01:55 +0000
1575@@ -1,6 +1,8 @@
1576 /*
1577- * Copyright (C) 2013 Daniel Holm <d.holmen@gmail.com>
1578- * Victor Thompson <victor.thompson@gmail.com>
1579+ * Copyright (C) 2013, 2016
1580+ * Andrew Hayzen <ahayzen@gmail.com>
1581+ * Daniel Holm <d.holmen@gmail.com>
1582+ * Victor Thompson <victor.thompson@gmail.com>
1583 *
1584 * This program is free software; you can redistribute it and/or modify
1585 * it under the terms of the GNU General Public License as published by
1586@@ -230,6 +232,11 @@
1587 for (j = 0; j < rs.rows.length; j++) {
1588 var dbItem = rs.rows.item(j)
1589
1590+ // ms2 doesn't expect the URI scheme so strip file://
1591+ if (dbItem.filename.indexOf("file://") === 0) {
1592+ dbItem.filename = dbItem.filename.substr(7);
1593+ }
1594+
1595 if (musicStore.lookup(decodeFileURI(dbItem.filename)) === null) {
1596 erroneousTracks.push(dbItem.i);
1597 } else {
1598
1599=== modified file 'app/music-app.qml'
1600--- app/music-app.qml 2015-12-03 14:14:14 +0000
1601+++ app/music-app.qml 2016-01-26 00:01:55 +0000
1602@@ -1,5 +1,5 @@
1603 /*
1604- * Copyright (C) 2013, 2014, 2015
1605+ * Copyright (C) 2013, 2014, 2015, 2016
1606 * Andrew Hayzen <ahayzen@gmail.com>
1607 * Daniel Holm <d.holmen@gmail.com>
1608 * Victor Thompson <victor.thompson@gmail.com>
1609@@ -22,7 +22,7 @@
1610 import Ubuntu.Components.Popups 1.3
1611 import Ubuntu.MediaScanner 0.1
1612 import Qt.labs.settings 1.0
1613-import QtMultimedia 5.0
1614+import QtMultimedia 5.6
1615 import QtQuick.LocalStorage 2.0
1616 import QtGraphicalEffects 1.0
1617 import "logic/stored-request.js" as StoredRequest
1618@@ -56,6 +56,11 @@
1619 }
1620 }
1621
1622+ Connections { // save the current queueIndex for when the app restarts
1623+ target: player.mediaPlayer.playlist
1624+ onCurrentIndexChanged: startupSettings.queueIndex = player.mediaPlayer.playlist.currentIndex
1625+ }
1626+
1627 // Global keyboard shortcuts
1628 focus: true
1629 Keys.onPressed: {
1630@@ -73,33 +78,33 @@
1631
1632 switch (event.key) {
1633 case Qt.Key_Right: // Alt+Right Seek forward +10secs
1634- position = player.position + 10000 < player.duration
1635- ? player.position + 10000 : player.duration;
1636- player.seek(position);
1637+ position = player.mediaPlayer.position + 10000 < player.mediaPlayer.duration
1638+ ? player.mediaPlayer.position + 10000 : player.mediaPlayer.duration;
1639+ player.mediaPlayer.seek(position);
1640 break;
1641 case Qt.Key_Left: // Alt+Left Seek backwards -10secs
1642- position = player.position - 10000 > 0
1643- ? player.position - 10000 : 0;
1644- player.seek(position);
1645+ position = player.mediaPlayer.position - 10000 > 0
1646+ ? player.mediaPlayer.position - 10000 : 0;
1647+ player.mediaPlayer.seek(position);
1648 break;
1649 }
1650 }
1651 else if(event.modifiers === Qt.ControlModifier) {
1652 switch (event.key) {
1653 case Qt.Key_Left: // Ctrl+Left Previous Song
1654- player.previousSong(true);
1655+ player.mediaPlayer.playlist.previousWrapper();
1656 break;
1657 case Qt.Key_Right: // Ctrl+Right Next Song
1658- player.nextSong(true, true);
1659+ player.mediaPlayer.playlist.nextWrapper();
1660 break;
1661 case Qt.Key_Up: // Ctrl+Up Volume up
1662- player.volume = player.volume + .1 > 1 ? 1 : player.volume + .1
1663+ player.mediaPlayer.volume = player.mediaPlayer.volume + .1 > 1 ? 1 : player.mediaPlayer.volume + .1
1664 break;
1665 case Qt.Key_Down: // Ctrl+Down Volume down
1666- player.volume = player.volume - .1 < 0 ? 0 : player.volume - .1
1667+ player.mediaPlayer.volume = player.mediaPlayer.volume - .1 < 0 ? 0 : player.mediaPlayer.volume - .1
1668 break;
1669 case Qt.Key_R: // Ctrl+R Repeat toggle
1670- player.repeat = !player.repeat
1671+ player.mediaPlayer.repeat = !player.mediaPlayer.repeat
1672 break;
1673 case Qt.Key_F: // Ctrl+F Show Search popup
1674 if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
1675@@ -116,13 +121,13 @@
1676 tabs.pushNowPlaying()
1677 break;
1678 case Qt.Key_P: // Ctrl+P Toggle playing state
1679- player.toggle();
1680+ player.mediaPlayer.toggle();
1681 break;
1682 case Qt.Key_Q: // Ctrl+Q Quit the app
1683 Qt.quit();
1684 break;
1685 case Qt.Key_U: // Ctrl+U Shuffle toggle
1686- player.shuffle = !player.shuffle
1687+ player.mediaPlayer.shuffle = !player.mediaPlayer.shuffle
1688 break;
1689 }
1690 }
1691@@ -152,15 +157,15 @@
1692 id: nextAction
1693 text: i18n.tr("Next")
1694 keywords: i18n.tr("Next Track")
1695- onTriggered: player.nextSong()
1696+ onTriggered: player.mediaPlayer.playlist.nextWrapper()
1697 }
1698 Action {
1699 id: playsAction
1700- text: player.playbackState === MediaPlayer.PlayingState ?
1701- i18n.tr("Pause") : i18n.tr("Play")
1702- keywords: player.playbackState === MediaPlayer.PlayingState ?
1703- i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
1704- onTriggered: player.toggle()
1705+ text: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
1706+ ? i18n.tr("Pause") : i18n.tr("Play")
1707+ keywords: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
1708+ ? i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
1709+ onTriggered: player.mediaPlayer.toggle()
1710 }
1711 Action {
1712 id: backAction
1713@@ -175,13 +180,13 @@
1714 id: prevAction
1715 text: i18n.tr("Previous")
1716 keywords: i18n.tr("Previous Track")
1717- onTriggered: player.previousSong()
1718+ onTriggered: player.mediaPlayer.playlist.previousWrapper()
1719 }
1720 Action {
1721 id: stopAction
1722 text: i18n.tr("Stop")
1723 keywords: i18n.tr("Stop Playback")
1724- onTriggered: player.stop()
1725+ onTriggered: player.mediaPlayer.stop()
1726 }
1727
1728 actions: [nextAction, playsAction, prevAction, stopAction, backAction]
1729@@ -203,23 +208,6 @@
1730 width: units.gu(100)
1731 height: units.gu(80)
1732
1733- WorkerModelLoader {
1734- id: queueLoaderWorker
1735- canLoad: false
1736- model: trackQueue.model
1737- syncFactor: 10
1738-
1739- onPreLoadCompleteChanged: {
1740- if (preLoadComplete) {
1741- player.currentIndex = queueIndex
1742-
1743- if (list[queueIndex] !== undefined) {
1744- player.setSource(list[queueIndex].filename)
1745- }
1746- }
1747- }
1748- }
1749-
1750 WorkerWaiter {
1751 id: waitForWorker
1752 }
1753@@ -229,14 +217,29 @@
1754 customdebug("Version "+appVersion) // print the curren version
1755
1756 Library.createRecent() // initialize recent
1757+ Library.createQueue() // create queue if it doesn't exist
1758
1759 // initialize playlists
1760 Playlists.initializePlaylist()
1761
1762 if (!args.values.url) {
1763- // allow the queue loader to start
1764- queueLoaderWorker.canLoad = !Library.isQueueEmpty()
1765- queueLoaderWorker.list = Library.getQueue()
1766+ // load the previous queue as there are no args
1767+
1768+ // FIXME: load and save do not work yet pad.lv/1510225
1769+ // so use our localstorage method for now
1770+ // player.mediaPlayer.playlist.load("/home/phablet/.local/share/com.ubuntu.music/queue.m3u")
1771+ // use onloaded() and onLoadFailed() to confirm it is complete
1772+
1773+ if (!Library.isQueueEmpty()) {
1774+ console.debug("*** Restoring library queue");
1775+ player.mediaPlayer.playlist.addItems(Library.getQueue());
1776+
1777+ player.mediaPlayer.playlist.setCurrentIndex(queueIndex);
1778+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PausedState);
1779+ }
1780+ else {
1781+ console.debug("Queue is empty, not loading any recent tracks");
1782+ }
1783 }
1784
1785 // everything else
1786@@ -301,23 +304,6 @@
1787 }
1788 }
1789
1790- function addQueueFromModel(model)
1791- {
1792- // TODO: remove once playlists uses U1DB
1793- if (model.hasOwnProperty("linkLibraryListModel")) {
1794- model = model.linkLibraryListModel;
1795- }
1796-
1797- var items = []
1798-
1799- for (var i=0; i < model.rowCount; i++) {
1800- items.push(model.get(i, model.RoleModelData))
1801- }
1802-
1803- // Add model to queue storage
1804- trackQueue.appendList(items)
1805- }
1806-
1807 // Converts an duration in ms to a formated string ("minutes:seconds")
1808 function durationToString(duration) {
1809 var minutes = Math.floor((duration/1000) / 60);
1810@@ -336,20 +322,13 @@
1811 album: model.album,
1812 art: model.art,
1813 author: model.author,
1814- filename: model.filename,
1815+ filename: model.filename || model.source,
1816 title: model.title
1817 };
1818 }
1819
1820- function trackClicked(model, index, play, clear) {
1821- // Stop queue loading in the background
1822- queueLoaderWorker.canLoad = false
1823-
1824- if (queueLoaderWorker.processing > 0) {
1825- waitForWorker.workerStop(queueLoaderWorker, trackClicked, [model, index, play, clear])
1826- return;
1827- }
1828-
1829+ // Clear the queue, queue this model and play the specific index
1830+ function trackClicked(model, index, play) {
1831 // TODO: remove once playlists uses U1DB
1832 if (model.hasOwnProperty("linkLibraryListModel")) {
1833 model = model.linkLibraryListModel;
1834@@ -358,64 +337,50 @@
1835 var file = Qt.resolvedUrl(model.get(index, model.RoleModelData).filename);
1836
1837 play = play === undefined ? true : play // default play to true
1838- clear = clear === undefined ? false : clear // force clear and will ignore player.toggle()
1839-
1840- if (!clear) {
1841- // If same track and on Now playing page then toggle
1842- if (mainPageStack.currentPage.title === i18n.tr("Now playing")
1843- && trackQueue.model.get(player.currentIndex) !== undefined
1844- && Qt.resolvedUrl(trackQueue.model.get(player.currentIndex).filename) === file) {
1845- player.toggle()
1846- return;
1847- }
1848- }
1849-
1850- trackQueue.clear(); // clear the old model
1851-
1852- addQueueFromModel(model);
1853+
1854+ player.mediaPlayer.playlist.clearWrapper(); // clear the old model
1855+ player.mediaPlayer.playlist.setCurrentIndex(index);
1856+ player.mediaPlayer.playlist.addItemsFromModel(model);
1857
1858 if (play) {
1859- player.playSong(file, index);
1860+ // Set the pending state for the playlist
1861+ // this will be set once the currentIndex has been appened to the playlist
1862+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
1863
1864 // Show the Now playing page and make sure the track is visible
1865 tabs.pushNowPlaying();
1866 }
1867- else {
1868- player.setSource(file);
1869- }
1870 }
1871
1872+ // Play or pause a current track in the queue
1873+ // - the index has been tapped by the user
1874 function trackQueueClick(index) {
1875- if (player.currentIndex === index) {
1876- player.toggle();
1877- }
1878- else {
1879- player.playSong(trackQueue.model.get(index).filename, index);
1880- }
1881- }
1882-
1883- function playRandomSong(shuffle)
1884- {
1885- trackQueue.clear();
1886-
1887- var now = new Date();
1888- var seed = now.getSeconds();
1889- var index = Math.floor(allSongsModel.rowCount * Math.random(seed));
1890-
1891- player.shuffle = shuffle === undefined ? true : shuffle;
1892-
1893- trackClicked(allSongsModel, index, true)
1894- }
1895-
1896- function shuffleModel(model)
1897- {
1898- var now = new Date();
1899- var seed = now.getSeconds();
1900- var index = Math.floor(model.count * Math.random(seed));
1901-
1902+ if (player.mediaPlayer.playlist.currentIndex === index) {
1903+ player.mediaPlayer.toggle();
1904+ } else {
1905+ player.mediaPlayer.playlist.setCurrentIndex(index);
1906+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
1907+ }
1908+ }
1909+
1910+ // Clear the queue and play a random track from this model
1911+ // - user has selected "Shuffle" in album/artists or "Tap to play random"
1912+ function playRandomSong(model)
1913+ {
1914+ // If no model is given use all the tracks
1915+ if (model === undefined) {
1916+ model = allSongsModel;
1917+ }
1918+
1919+ player.mediaPlayer.playlist.clearWrapper();
1920+ player.mediaPlayer.playlist.addItemsFromModel(model);
1921 player.shuffle = true;
1922
1923- trackClicked(model, index, true)
1924+ // Once the model count has been reached in the queue
1925+ // shuffle the model
1926+ player.mediaPlayer.playlist.setPendingShuffle(model.count);
1927+
1928+ tabs.pushNowPlaying();
1929 }
1930
1931 // Wrapper function around decodeURIComponent() to prevent exceptions
1932@@ -453,8 +418,15 @@
1933 var removed = []
1934
1935 // Find tracks from the queue that aren't in ms2 anymore
1936- for (i=0; i < trackQueue.model.count; i++) {
1937- if (musicStore.lookup(decodeFileURI(trackQueue.model.get(i).filename)) === null) {
1938+ for (i=0; i < player.mediaPlayer.playlist.count; i++) {
1939+ var file = decodeFileURI(player.mediaPlayer.playlist.itemSource(i));
1940+
1941+ // ms2 doesn't expect the URI scheme so strip file://
1942+ if (file.indexOf("file://") === 0) {
1943+ file = file.substr(7);
1944+ }
1945+
1946+ if (musicStore.lookup(file) === null) {
1947 removed.push(i)
1948 }
1949 }
1950@@ -462,7 +434,7 @@
1951 // If there are removed tracks then remove them from the queue and store
1952 if (removed.length > 0) {
1953 console.debug("Removed queue:", JSON.stringify(removed))
1954- trackQueue.removeQueueList(removed)
1955+ player.mediaPlayer.playlist.removeItemsWrapper(removed.slice());
1956 }
1957
1958 // Loop through playlists, getPlaylistTracks will remove any tracks that don't exist
1959@@ -529,7 +501,7 @@
1960 if (status === SongsModel.Ready) {
1961 // Play album it tracks exist
1962 if (rowCount > 0 && selectedAlbum) {
1963- trackClicked(songsAlbumArtistModel, 0, true, true);
1964+ trackClicked(songsAlbumArtistModel, 0, true);
1965
1966 // Add album to recent list
1967 Library.addRecent(songsAlbumArtistModel.get(0, SongsModel.RoleModelData).album, "album")
1968@@ -579,105 +551,6 @@
1969 }
1970 }
1971
1972- // list of tracks on startup. This is just during development
1973- LibraryListModel {
1974- id: trackQueue
1975- objectName: "trackQueue"
1976-
1977- function append(listElement)
1978- {
1979- model.append(makeDict(listElement))
1980- Library.addQueueItem(listElement.filename)
1981- }
1982-
1983- function appendList(items)
1984- {
1985- for (var i=0; i < items.length; i++) {
1986- model.append(makeDict(items[i]))
1987- }
1988-
1989- Library.addQueueList(items)
1990- }
1991-
1992- function clear()
1993- {
1994- Library.clearQueue()
1995- model.clear()
1996-
1997- queueIndex = 0 // reset otherwise when you append and play 1 track it doesn't update correctly
1998- }
1999-
2000- // Optimised removeQueue for removing multiple tracks from the queue
2001- function removeQueueList(items)
2002- {
2003- var i;
2004-
2005- // Remove from the saved queue database
2006- Library.removeQueueList(items)
2007-
2008- // Sort the item indexes as loops below assume *numeric* sort
2009- items.sort(function(a,b) { return a - b })
2010-
2011- // Remove from the listmodel
2012- var startCount = trackQueue.model.count
2013-
2014- for (i=0; i < items.length; i++) {
2015- // use diff in count as sometimes the row is removed from the model
2016- trackQueue.model.remove(items[i] - (startCount - trackQueue.model.count));
2017- }
2018-
2019- // Update the currentIndex and playing status
2020-
2021- if (trackQueue.model.count === 0) {
2022- // Nothing in the queue so stop and pop the queue
2023- player.stop()
2024- mainPageStack.goBack()
2025- } else if (items.indexOf(player.currentIndex) > -1) {
2026- // Current track was removed
2027-
2028- var newIndex;
2029-
2030- // Find the first index that still exists before the currentIndex
2031- for (i=player.currentIndex - 1; i > -1; i--) {
2032- if (items.indexOf(i) === -1) {
2033- break;
2034- }
2035- }
2036-
2037- newIndex = i;
2038-
2039- // Shuffle index down as tracks were removed before the current
2040- for (i=newIndex; i > -1; i--) {
2041- if (items.indexOf(i) !== -1) {
2042- newIndex--;
2043- }
2044- }
2045-
2046- // Set this as the current track
2047- player.currentIndex = newIndex
2048-
2049- // Play the next track
2050- player.nextSong(player.isPlaying);
2051- } else {
2052- // Current track not in removed list
2053- // Check if the index needs to be shuffled down due to removals
2054-
2055- var before = 0
2056-
2057- for (i=0; i < items.length; i++) {
2058- if (items[i] < player.currentIndex) {
2059- before++;
2060- }
2061- }
2062-
2063- // Update the index
2064- player.currentIndex -= before;
2065- }
2066-
2067- queueIndex = player.currentIndex; // ensure saved index is up to date
2068- }
2069- }
2070-
2071 // TODO: list of playlists move to U1DB
2072 // create the listmodel to use for playlists
2073 LibraryListModel {
2074
2075=== modified file 'app/ui/AddToPlaylist.qml'
2076--- app/ui/AddToPlaylist.qml 2016-01-12 00:30:08 +0000
2077+++ app/ui/AddToPlaylist.qml 2016-01-26 00:01:55 +0000
2078@@ -17,7 +17,6 @@
2079 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2080 */
2081
2082-import QtMultimedia 5.0
2083 import QtQuick 2.4
2084 import Ubuntu.Components 1.3
2085 import QtQuick.LocalStorage 2.0
2086
2087=== modified file 'app/ui/ContentHubExport.qml'
2088--- app/ui/ContentHubExport.qml 2015-11-02 04:56:53 +0000
2089+++ app/ui/ContentHubExport.qml 2016-01-26 00:01:55 +0000
2090@@ -1,5 +1,5 @@
2091 /*
2092- * Copyright (C) 2015
2093+ * Copyright (C) 2015, 2016
2094 * Andrew Hayzen <ahayzen@gmail.com>
2095 * Victor Thompson <victor.thompson@gmail.com>
2096 *
2097@@ -136,25 +136,16 @@
2098 delegate: MusicListItem {
2099 id: track
2100 objectName: "tracksPageListItem" + index
2101- column: Column {
2102- Label {
2103- id: trackTitle
2104- color: styleMusic.common.music
2105- fontSize: "small"
2106- objectName: "tracktitle"
2107- text: model.title
2108- }
2109-
2110- Label {
2111- id: trackArtist
2112- color: styleMusic.common.subtitle
2113- fontSize: "x-small"
2114- text: model.author
2115- }
2116- }
2117 height: units.gu(7)
2118 imageSource: {"art": model.art}
2119 multiselectable: true
2120+ subtitle {
2121+ text: model.author
2122+ }
2123+ title {
2124+ objectName: "tracktitle"
2125+ text: model.title
2126+ }
2127
2128 onSelectedChanged: {
2129 if (singular) {
2130
2131=== modified file 'app/ui/NowPlaying.qml'
2132--- app/ui/NowPlaying.qml 2015-11-05 01:16:43 +0000
2133+++ app/ui/NowPlaying.qml 2016-01-26 00:01:55 +0000
2134@@ -1,5 +1,5 @@
2135 /*
2136- * Copyright (C) 2013, 2014, 2015
2137+ * Copyright (C) 2013, 2014, 2015, 2016
2138 * Andrew Hayzen <ahayzen@gmail.com>
2139 * Daniel Holm <d.holmen@gmail.com>
2140 * Victor Thompson <victor.thompson@gmail.com>
2141@@ -61,12 +61,12 @@
2142
2143 // Ensure that the listview has loaded before attempting to positionAt
2144 function ensureListViewLoaded() {
2145- if (queueListLoader.item.count === trackQueue.model.count) {
2146- positionAt(player.currentIndex);
2147+ if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
2148+ positionAt(player.mediaPlayer.playlist.currentIndex);
2149 } else {
2150 queueListLoader.item.onCountChanged.connect(function() {
2151- if (queueListLoader.item.count === trackQueue.model.count) {
2152- positionAt(player.currentIndex);
2153+ if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
2154+ positionAt(player.mediaPlayer.playlist.currentIndex);
2155 }
2156 })
2157 }
2158@@ -99,29 +99,30 @@
2159 name: "default"
2160 actions: [
2161 Action {
2162- enabled: trackQueue.model.count > 0
2163+ enabled: !player.mediaPlayer.playlist.empty
2164 iconName: "add-to-playlist"
2165 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
2166 text: i18n.tr("Add to playlist")
2167+ visible: !isListView
2168+
2169 onTriggered: {
2170 var items = []
2171
2172- items.push(makeDict(trackQueue.model.get(player.currentIndex)));
2173+ items.push(makeDict(player.metaForSource(player.mediaPlayer.playlist.currentItemSource)));
2174
2175 mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
2176 {"chosenElements": items})
2177 }
2178 },
2179 Action {
2180- enabled: trackQueue.model.count > 0
2181+ enabled: !player.mediaPlayer.playlist.empty
2182 iconName: "delete"
2183 objectName: "clearQueue"
2184 // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
2185 text: i18n.tr("Clear queue")
2186- onTriggered: {
2187- mainPageStack.goBack()
2188- trackQueue.clear()
2189- }
2190+ visible: isListView
2191+
2192+ onTriggered: player.mediaPlayer.playlist.clearWrapper()
2193 }
2194 ]
2195 PropertyChanges {
2196@@ -140,13 +141,14 @@
2197 // Remove the tracks from the queue
2198 // Use slice() to copy the list
2199 // so that the indexes don't change as they are removed
2200- trackQueue.removeQueueList(selectedIndices.slice())
2201+ player.mediaPlayer.playlist.removeItemsWrapper(selectedIndices.slice());
2202 }
2203 }
2204 ]
2205
2206 Loader {
2207 anchors {
2208+ bottom: nowPlayingToolbarLoader.top
2209 left: parent.left
2210 right: parent.right
2211 top: parent.top
2212@@ -155,7 +157,6 @@
2213
2214 property real headerHeight: units.gu(10.125) // FIXME: 10.125 is the header.height with the page sections
2215
2216- height: parent.height - headerHeight - units.gu(9.5)
2217 source: "../components/NowPlayingFullView.qml"
2218 visible: !isListView
2219 }
2220@@ -163,8 +164,11 @@
2221 Loader {
2222 id: queueListLoader
2223 anchors {
2224- bottomMargin: nowPlayingToolbarLoader.height + units.gu(2)
2225- fill: parent
2226+ bottom: nowPlayingToolbarLoader.top
2227+ bottomMargin: units.gu(2)
2228+ left: parent.left
2229+ right: parent.right
2230+ top: parent.top
2231 topMargin: units.gu(2)
2232 }
2233 asynchronous: true
2234@@ -182,4 +186,13 @@
2235 height: units.gu(10)
2236 source: "../components/NowPlayingToolbar.qml"
2237 }
2238+
2239+ Connections {
2240+ target: player.mediaPlayer.playlist
2241+ onEmptyChanged: {
2242+ if (player.mediaPlayer.playlist.empty) {
2243+ mainPageStack.goBack()
2244+ }
2245+ }
2246+ }
2247 }
2248
2249=== modified file 'app/ui/Playlists.qml'
2250--- app/ui/Playlists.qml 2016-01-12 00:30:08 +0000
2251+++ app/ui/Playlists.qml 2016-01-26 00:01:55 +0000
2252@@ -20,7 +20,6 @@
2253
2254 import QtQuick 2.4
2255 import Ubuntu.Components 1.3
2256-import QtMultimedia 5.0
2257 import QtQuick.LocalStorage 2.0
2258 import "../logic/playlists.js" as Playlists
2259 import "../components"
2260
2261=== modified file 'app/ui/Recent.qml'
2262--- app/ui/Recent.qml 2016-01-12 00:30:08 +0000
2263+++ app/ui/Recent.qml 2016-01-26 00:01:55 +0000
2264@@ -21,7 +21,6 @@
2265 import Ubuntu.Components 1.3
2266 import Ubuntu.MediaScanner 0.1
2267 import Ubuntu.Thumbnailer 0.1
2268-import QtMultimedia 5.0
2269 import QtQuick.LocalStorage 2.0
2270 import "../logic/meta-database.js" as Library
2271 import "../logic/playlists.js" as Playlists
2272
2273=== modified file 'app/ui/Songs.qml'
2274--- app/ui/Songs.qml 2016-01-09 12:49:43 +0000
2275+++ app/ui/Songs.qml 2016-01-26 00:01:55 +0000
2276@@ -21,7 +21,6 @@
2277 import Ubuntu.Components 1.3
2278 import Ubuntu.MediaScanner 0.1
2279 import Ubuntu.Thumbnailer 0.1
2280-import QtMultimedia 5.0
2281 import QtQuick.LocalStorage 2.0
2282 import "../logic/playlists.js" as Playlists
2283 import "../components"
2284@@ -107,8 +106,8 @@
2285
2286 onItemClicked: {
2287 if (songsPage.state === "search") { // only play single track when searching
2288- trackQueue.clear()
2289- trackQueue.append(songsModelFilter.get(index))
2290+ player.mediaPlayer.playlist.clearWrapper();
2291+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(songsModelFilter.get(index).filename));
2292 trackQueueClick(0)
2293 } else {
2294 trackClicked(songsModelFilter, index) // play track
2295
2296=== modified file 'debian/changelog'
2297--- debian/changelog 2016-01-12 00:26:51 +0000
2298+++ debian/changelog 2016-01-26 00:01:55 +0000
2299@@ -1,9 +1,12 @@
2300 music-app (2.3) UNRELEASED; urgency=medium
2301-
2302+
2303 [ Andrew Hayzen ]
2304 * Release 2.2ubuntu2 and start on 2.3
2305 * Use ListItemLayout for listitems to improve performance and match design guidelines (LP: #1526247).
2306 * Switch to using Qt GridView instead of custom CardView
2307+ * Add support for media-hub background playlists
2308+ * Bump framework to 15.04.3-qml
2309+ * Bump QtMultimedia import to 5.6
2310
2311 [ Ken VanDine ]
2312 * Install the content-hub json file in the correct place for peer registry
2313
2314=== modified file 'debian/control'
2315--- debian/control 2014-10-22 16:24:01 +0000
2316+++ debian/control 2016-01-26 00:01:55 +0000
2317@@ -18,6 +18,7 @@
2318 gstreamer1.0-fluendo-mp3,
2319 qmlscene,
2320 qml-module-qt-labs-settings,
2321+ qml-module-qtmultimedia (>=5.5.1-1ubuntu2),
2322 qtdeclarative5-ubuntu-content0.1,
2323 qtdeclarative5-localstorage-plugin,
2324 qtdeclarative5-particles-plugin,
2325
2326=== modified file 'manifest.json.in'
2327--- manifest.json.in 2015-12-03 14:14:14 +0000
2328+++ manifest.json.in 2016-01-26 00:01:55 +0000
2329@@ -1,7 +1,7 @@
2330 {
2331 "architecture": "all",
2332 "description": "A music application for ubuntu",
2333- "framework": "ubuntu-sdk-15.04.1-qml",
2334+ "framework": "ubuntu-sdk-15.04.3-qml",
2335 "hooks": {
2336 "music": {
2337 "apparmor": "apparmor.json",
2338
2339=== modified file 'tests/autopilot/music_app/__init__.py'
2340--- tests/autopilot/music_app/__init__.py 2016-01-12 00:30:08 +0000
2341+++ tests/autopilot/music_app/__init__.py 2016-01-26 00:01:55 +0000
2342@@ -54,7 +54,6 @@
2343 # Use only objectName due to bug 1350532 as it is MainView12
2344 self.main_view = self.app.wait_select_single(
2345 objectName="musicMainView")
2346- self.player = self.app.select_single(Player, objectName='player')
2347
2348 def get_add_to_playlist_page(self):
2349 return self.app.wait_select_single(AddToPlaylist,
2350@@ -95,8 +94,7 @@
2351 Playlists, objectName='playlistsPage')
2352
2353 def get_queue_count(self):
2354- return self.main_view.select_single("LibraryListModel",
2355- objectName="trackQueue").count
2356+ return self.player.count
2357
2358 def get_songs_view(self):
2359 return self.app.wait_select_single(SongsView, objectName="songsPage")
2360@@ -121,6 +119,11 @@
2361 objectName="LoadingSpinner").running and
2362 self.main_view.select_single("*", "allSongsModel").populated)
2363
2364+ @property
2365+ def player(self):
2366+ # Get new player each time as data changes (eg currentMeta)
2367+ return self.app.select_single(Player, objectName='player')
2368+
2369 def populate_queue(self):
2370 tracksPage = self.get_songs_page() # switch to track tab
2371
2372@@ -263,15 +266,16 @@
2373 class Player(UbuntuUIToolkitCustomProxyObjectBase):
2374 """Autopilot helper for Player"""
2375
2376+ @property
2377+ def currentMeta(self):
2378+ return self.select_single("*", objectName="currentMeta")
2379+
2380
2381 class NowPlaying(MusicPage):
2382 """ Autopilot helper for now playing page """
2383 def __init__(self, *args):
2384 super(NowPlaying, self).__init__(*args)
2385
2386- root = self.get_root_instance()
2387- self.player = root.select_single(Player, objectName="player")
2388-
2389 self.visible.wait_for(True)
2390
2391 @ensure_now_playing_full
2392@@ -279,6 +283,9 @@
2393 def click_forward_button(self):
2394 return self.wait_select_single("*", objectName="forwardShape")
2395
2396+ def click_full_view(self):
2397+ self.main_view.get_header().switch_to_section_by_index(0)
2398+
2399 @ensure_now_playing_full
2400 @click_object
2401 def click_play_button(self):
2402@@ -289,6 +296,9 @@
2403 def click_previous_button(self):
2404 return self.wait_select_single("*", objectName="previousShape")
2405
2406+ def click_queue_view(self):
2407+ self.main_view.get_header().switch_to_section_by_index(1)
2408+
2409 @ensure_now_playing_full
2410 @click_object
2411 def click_repeat_button(self):
2412@@ -299,17 +309,21 @@
2413 def click_shuffle_button(self):
2414 return self.wait_select_single("*", objectName="shuffleShape")
2415
2416- def click_full_view(self):
2417- self.main_view.get_header().switch_to_section_by_index(0)
2418-
2419- def click_queue_view(self):
2420- self.main_view.get_header().switch_to_section_by_index(1)
2421+ @click_object
2422+ def click_track(self, i):
2423+ return self.get_track(i)
2424
2425 @ensure_now_playing_list
2426 def get_track(self, i):
2427 return (self.wait_select_single(MusicListItem,
2428 objectName="nowPlayingListItem" + str(i)))
2429
2430+ @property
2431+ def player(self):
2432+ # Get new player each time as data changes (eg currentMeta)
2433+ root = self.get_root_instance()
2434+ return root.select_single(Player, objectName="player")
2435+
2436 @ensure_now_playing_full
2437 def seek_to(self, percentage):
2438 progress_bar = self.wait_select_single(
2439@@ -415,6 +429,11 @@
2440
2441
2442 class Dialog(UbuntuUIToolkitCustomProxyObjectBase):
2443+ def __init__(self, *args):
2444+ super(Dialog, self).__init__(*args)
2445+
2446+ self.visible.wait_for(True)
2447+
2448 @click_object
2449 def click_new_playlist_dialog_create_button(self):
2450 return self.wait_select_single(
2451
2452=== modified file 'tests/autopilot/music_app/tests/test_music.py'
2453--- tests/autopilot/music_app/tests/test_music.py 2016-01-12 00:30:08 +0000
2454+++ tests/autopilot/music_app/tests/test_music.py 2016-01-26 00:01:55 +0000
2455@@ -77,9 +77,9 @@
2456 self.app.populate_queue() # populate queue
2457
2458 # Check current meta data is correct
2459- self.assertThat(self.player.currentMetaTitle,
2460+ self.assertThat(self.player.currentMeta.title,
2461 Eventually(Equals(self.tracks[0]["title"])))
2462- self.assertThat(self.player.currentMetaArtist,
2463+ self.assertThat(self.player.currentMeta.author,
2464 Eventually(Equals(self.tracks[0]["artist"])))
2465
2466 def test_play_pause_library(self):
2467@@ -158,52 +158,44 @@
2468
2469 now_playing_page = self.app.get_now_playing_page()
2470
2471+ # check the track was playing
2472+ self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2473+
2474+ # select pause and check the player has stopped
2475+ now_playing_page.click_play_button()
2476+ self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2477+
2478 # save original song data for later
2479- org_title = self.player.currentMetaTitle
2480- org_artist = self.player.currentMetaArtist
2481-
2482- # check original track
2483- self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2484- logger.debug("Original Song %s, %s" % (org_title, org_artist))
2485-
2486- # select pause and check the player has stopped
2487- now_playing_page.click_play_button()
2488- self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2489+ orig_title = self.player.currentMeta.title
2490+ orig_artist = self.player.currentMeta.author
2491+ logger.debug("Original Song %s, %s" % (orig_title, orig_artist))
2492
2493 now_playing_page.set_shuffle(False) # ensure shuffe is off
2494
2495 # goal is to go back and forth and ensure 2 different songs
2496 now_playing_page.click_forward_button()
2497- self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2498
2499- # select pause and check the player has stopped
2500- now_playing_page.click_play_button()
2501+ # check the player is still stopped
2502 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2503
2504 # ensure different song
2505- self.assertThat(self.player.currentMetaTitle,
2506- Eventually(NotEquals(org_title)))
2507- self.assertThat(self.player.currentMetaArtist,
2508- Eventually(NotEquals(org_artist)))
2509-
2510- logger.debug("Next Song %s, %s" % (self.player.currentMetaTitle,
2511- self.player.currentMetaArtist))
2512-
2513- now_playing_page.seek_to(0) # seek to 0 (start)
2514-
2515- # select previous and ensure the track is playing
2516+ self.assertThat(self.player.currentMeta.title,
2517+ Eventually(NotEquals(orig_title)))
2518+ self.assertThat(self.player.currentMeta.author,
2519+ Eventually(NotEquals(orig_artist)))
2520+
2521+ logger.debug("Next Song %s, %s" % (self.player.currentMeta.title,
2522+ self.player.currentMeta.author))
2523+
2524+ # select previous and ensure the track is stopped
2525 now_playing_page.click_previous_button()
2526- self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2527-
2528- # select pause and check the player has stopped
2529- now_playing_page.click_play_button()
2530 self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2531
2532 # ensure we're back to original song
2533- self.assertThat(self.player.currentMetaTitle,
2534- Eventually(Equals(org_title)))
2535- self.assertThat(self.player.currentMetaArtist,
2536- Eventually(Equals(org_artist)))
2537+ self.assertThat(self.player.currentMeta.title,
2538+ Eventually(Equals(orig_title)))
2539+ self.assertThat(self.player.currentMeta.author,
2540+ Eventually(Equals(orig_artist)))
2541
2542 def test_mp3(self):
2543 """ Test that mp3 "plays" or at least doesn't crash on load """
2544@@ -231,7 +223,7 @@
2545 Eventually(Equals(initial_tracks_count + 1)))
2546
2547 # Ensure the current track is mp3
2548- self.assertThat(self.player.source.endswith("mp3"),
2549+ self.assertThat(self.player.currentItemSource.endswith("mp3"),
2550 Equals(True))
2551
2552 # Start playing the track (click from toolbar)
2553@@ -244,9 +236,9 @@
2554 toolbar.click_play_button()
2555
2556 # Check current meta data is correct
2557- self.assertThat(self.player.currentMetaTitle,
2558+ self.assertThat(self.player.currentMeta.title,
2559 Eventually(Equals(self.tracks[i]["title"])))
2560- self.assertThat(self.player.currentMetaArtist,
2561+ self.assertThat(self.player.currentMeta.author,
2562 Eventually(Equals(self.tracks[i]["artist"])))
2563
2564 def test_shuffle(self):
2565@@ -258,30 +250,31 @@
2566
2567 now_playing_page = self.app.get_now_playing_page()
2568
2569+ now_playing_page.set_repeat(True)
2570+ now_playing_page.set_shuffle(True)
2571+
2572 # pause the track if it is playing
2573 if self.player.isPlaying:
2574 now_playing_page.click_play_button()
2575
2576 self.player.isPlaying.wait_for(False)
2577
2578- now_playing_page.set_shuffle(True) # enable shuffle
2579-
2580- # save original song metadata
2581- org_title = self.player.currentMetaTitle
2582- org_artist = self.player.currentMetaArtist
2583-
2584- logger.debug("Original Song %s, %s" % (org_title, org_artist))
2585-
2586 count = 0
2587+ previous_index = -1
2588
2589- # loop while the track is the same if different then a shuffle occurred
2590- while (org_title == self.player.currentMetaTitle and
2591- org_artist == self.player.currentMetaArtist):
2592+ # Keep going until the index is not previous + 1 (with wrapping)
2593+ # or previous == currentIndex (to ensure shuffle is working)
2594+ while ((previous_index + 1) % self.player.count ==
2595+ self.player.currentIndex or
2596+ previous_index == self.player.currentIndex):
2597 logger.debug("count %s" % (count))
2598
2599 # check count is valid
2600 self.assertThat(count, LessThan(100))
2601
2602+ # store this index as the previous
2603+ previous_index = self.player.currentIndex
2604+
2605 # select next track
2606 now_playing_page.click_forward_button()
2607
2608@@ -289,28 +282,13 @@
2609 if self.player.isPlaying:
2610 now_playing_page.click_play_button()
2611
2612- # check it is paused
2613- self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2614-
2615- # save current file so we can check it goes back
2616- source = self.player.currentMetaFile
2617-
2618- # select previous track while will break if this previous track
2619- # is different and therefore a shuffle has occurred
2620- now_playing_page.click_previous_button()
2621-
2622- # pause the track if it is playing
2623- if self.player.isPlaying:
2624- now_playing_page.click_play_button()
2625-
2626- # check it is paused
2627- self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
2628-
2629- # check the file has actually changed
2630- self.assertThat(self.player.currentMetaFile,
2631- Eventually(NotEquals(source)))
2632-
2633- count += 1 # increment count
2634+ self.player.isPlaying.wait_for(False)
2635+
2636+ # toggle shuffle to increase random
2637+ now_playing_page.set_shuffle(False)
2638+ now_playing_page.set_shuffle(True)
2639+
2640+ count += 1
2641
2642 def test_show_albums_page(self):
2643 """tests navigating to the Albums tab and displaying the album page"""
2644@@ -648,7 +626,7 @@
2645 now_playing_page.click_forward_button()
2646
2647 # Make sure we loop back to first song after last song ends
2648- self.assertThat(self.player.currentMetaTitle,
2649+ self.assertThat(self.player.currentMeta.title,
2650 Eventually(Equals(self.tracks[0]["title"])))
2651 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2652
2653@@ -667,7 +645,7 @@
2654 now_playing_page.click_forward_button()
2655
2656 # Make sure we loop back to first song after last song ends
2657- self.assertThat(self.player.currentMetaTitle,
2658+ self.assertThat(self.player.currentMeta.title,
2659 Eventually(Equals(self.tracks[0]["title"])))
2660 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2661
2662@@ -681,16 +659,16 @@
2663 now_playing_page.set_shuffle(False)
2664 now_playing_page.set_repeat(True)
2665
2666- initial_song = self.player.currentMetaTitle
2667+ initial_song = self.player.currentMeta.title
2668 now_playing_page.click_previous_button()
2669
2670 # If we're far enough into a song, pressing prev just takes us to the
2671 # beginning of that track. In that case, hit prev again to actually
2672 # skip over the track.
2673- if self.player.currentMetaTitle == initial_song:
2674+ if self.player.currentMeta.title == initial_song:
2675 now_playing_page.click_previous_button()
2676
2677- self.assertThat(self.player.currentMetaTitle,
2678+ self.assertThat(self.player.currentMeta.title,
2679 Eventually(Equals(self.tracks[-1]["title"])))
2680 self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
2681
2682@@ -702,19 +680,23 @@
2683 now_playing_page = self.app.get_now_playing_page()
2684
2685 self.player.isPlaying.wait_for(True) # ensure the track is playing
2686- self.player.position.wait_for(GreaterThan(5000)) # wait until > 5s
2687+
2688+ # wait until > 5s
2689+ self.player.position.wait_for(GreaterThan(5000))
2690
2691 now_playing_page.click_play_button() # pause the track
2692 self.player.isPlaying.wait_for(False) # ensure the track has paused
2693
2694- source = self.player.source # store current source
2695+ source = self.player.currentMeta.filename # store current source
2696
2697 now_playing_page.click_previous_button() # click previous
2698
2699 # resume the track (to ensure position updates)
2700 now_playing_page.click_play_button()
2701
2702- self.player.position.wait_for(LessThan(5000)) # wait until < 5s
2703+ # wait until < 5s
2704+ self.player.position.wait_for(LessThan(5000))
2705
2706 # Check that the source is the same
2707- self.assertThat(self.player.source, Eventually(Equals(source)))
2708+ self.assertThat(self.player.currentMeta.filename,
2709+ Eventually(Equals(source)))

Subscribers

People subscribed via source and target branches

to all changes: