Merge lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
- media-hub-bg-playlists-rework
- Merge into trunk
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 |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jenkins Bot | continuous-integration | Approve | |
Victor Thompson | Approve | ||
Review via email:
|
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andrew Hayzen (ahayzen) wrote : | # |
I got autopilot passing \o/
autopilot PASS
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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(
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'.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:932
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 933. By Andrew Hayzen
-
* Fix for ContentHubExpor
t.qml missing migration to listitem layout
* Fix for ContentHubHelper.qml referencing queue worker and not pushing now playing page
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:933
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
- 934. By Andrew Hayzen
-
* Add qml-module-
qtmultimedia (>=5.5.1-1ubuntu2) to control
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:934
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 935. By Andrew Hayzen
-
* Update copyright dates
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
- 936. By Andrew Hayzen
-
* Fix Play All/Shuffle All/Queue All so that they only perform their action when the model.count > 0
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:935
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Victor Thompson (vthompson) wrote : | # |
Both the "Shuffle" and "Queue all" buttons now do not work for a playlist.
- 937. By Andrew Hayzen
-
* Fix for Shuffle All and Queue All not working with playlists due to them using LibraryListModel
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:936
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:937
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:939
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Victor Thompson (vthompson) wrote : | # |
This looks good now. We should figure out the Jenkins failure before landing, however.
- 940. By Andrew Hayzen
-
* Wait for dialog to be visible in autopilot
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:940
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andrew Hayzen (ahayzen) wrote : | # |
I have reported bug 1536361 for the keyboard issue.
- 941. By Andrew Hayzen
-
* Fix for usermetrics
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:941
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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://
TypeError: Cannot call method 'indexOf' of null^M
file://
r: Cannot read property 'showToolbar' of null^M
Screenshot: http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:942
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
- 943. By Andrew Hayzen
-
* Remove play + pause in test_next_previous that are now redundant due to how bgplaylists works
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:943
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jenkins Bot (ubuntu-core-apps-jenkins-bot) : | # |
Preview Diff
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))) |
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.