← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

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

 

Andrew Hayzen has proposed merging lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app.

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

Requested reviews:
  Victor Thompson (vthompson)
Related bugs:
  Bug #1251624 in Ubuntu Music App: "back button should not be random when in shuffle mode"
  https://bugs.launchpad.net/music-app/+bug/1251624
  Bug #1480280 in Ubuntu Music App: "Songs repeated when listening in shuffle mode"
  https://bugs.launchpad.net/music-app/+bug/1480280
  Bug #1500457 in Ubuntu Music App: "Pressing previous button sets duration to 0"
  https://bugs.launchpad.net/music-app/+bug/1500457
  Bug #1534172 in Ubuntu Music App: "With bgplaylists if an alert sounds, the playback stops and the queue is lost"
  https://bugs.launchpad.net/music-app/+bug/1534172

For more details, see:
https://code.launchpad.net/~music-app-dev/music-app/media-hub-bg-playlists-rework/+merge/275912

* Add support for media-hub background playlists

JUST NEED THIS FOR DIFF
-- 
Your team Music App Developers is subscribed to branch lp:music-app.
=== modified file 'app/components/BlurredBackground.qml'
--- app/components/BlurredBackground.qml	2015-08-12 23:36:44 +0000
+++ app/components/BlurredBackground.qml	2016-01-15 18:03:59 +0000
@@ -25,7 +25,7 @@
 Item {
     width: parent.width
 
-    property string art  // : player.currentMetaFile === "" ? Qt.resolvedUrl("../graphics/music-app-cover@xxxxxx") : player.currentMetaArt
+    property string art
 
     // dark layer
     Rectangle {

=== modified file 'app/components/Flickables/MultiSelectListView.qml'
--- app/components/Flickables/MultiSelectListView.qml	2015-10-18 18:16:05 +0000
+++ app/components/Flickables/MultiSelectListView.qml	2016-01-15 18:03:59 +0000
@@ -25,6 +25,10 @@
     // so we need to expose if in multiselect mode for the header states
     state: ViewItems.selectMode ? "multiselectable" : "normal"
 
+    // Describes if model.move() should be called when a list item drag is completed
+    // this is not required on the Queue as onReorder performs playlist.moveItem()
+    property bool autoModelMove: true
+
     signal clearSelection()
     signal closeSelection()
     signal reorder(int from, int to)
@@ -63,7 +67,10 @@
         if (event.status == ListItemDrag.Moving) {
             event.accept = false
         } else if (event.status == ListItemDrag.Dropped) {
-            model.move(event.from, event.to, 1);
+            if (autoModelMove) {  // check the model should auto move
+                model.move(event.from, event.to, 1);
+            }
+
             reorder(event.from, event.to)
         }
     }

=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
--- app/components/HeadState/MultiSelectHeadState.qml	2015-10-18 18:16:05 +0000
+++ app/components/HeadState/MultiSelectHeadState.qml	2016-01-15 18:03:59 +0000
@@ -59,14 +59,14 @@
             visible: addToQueue
 
             onTriggered: {
-                var items = []
+                var items = [];
                 var indicies = listview.getSelectedIndices();
 
                 for (var i=0; i < indicies.length; i++) {
-                    items.push(listview.model.get(indicies[i], listview.model.RoleModelData));
+                    items.push(Qt.resolvedUrl(listview.model.get(indicies[i], listview.model.RoleModelData).filename));
                 }
 
-                trackQueue.appendList(items)
+                player.mediaPlayer.playlist.addItems(items);
 
                 listview.closeSelection()
             }

=== modified file 'app/components/Helpers/ContentHubHelper.qml'
--- app/components/Helpers/ContentHubHelper.qml	2015-10-23 03:08:43 +0000
+++ app/components/Helpers/ContentHubHelper.qml	2016-01-15 18:03:59 +0000
@@ -220,14 +220,17 @@
             else {
                 stopTimer();
 
-                trackQueue.clear();
+                player.mediaPlayer.playlist.clearWrapper();
+
+                var items = [];
 
                 for (i=0; i < searchPaths.length; i++) {
-                    model = musicStore.lookup(decodeFileURI(searchPaths[i]))
-
-                    trackQueue.append(makeDict(model));
+                    // Don't need to check if in ms2 as that is done above
+                    items.push(Qt.resolvedUrl(decodeURIComponent(searchPaths[i])))
                 }
 
+                player.mediaPlayer.playlist.addItems(items);
+
                 trackQueueClick(0);
             }
         }

=== modified file 'app/components/Helpers/UriHandlerHelper.qml'
--- app/components/Helpers/UriHandlerHelper.qml	2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UriHandlerHelper.qml	2016-01-15 18:03:59 +0000
@@ -38,14 +38,6 @@
     }
 
     function processAlbum(uri) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, processAlbum, [uri])
-            return;
-        }
-
         selectedAlbum = true;
         var split = uri.split("/");
 
@@ -60,34 +52,26 @@
     }
 
     function processFile(uri, play) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, processFile, [uri, play])
-            return;
-        }
-
         // Lookup track in songs model
         var track = musicStore.lookup(decodeFileURI(uri));
 
         if (!track) {
             console.debug("Unknown file " + uri + ", skipping")
-            return;
-        }
-
-        if (play) {
-            // clear play queue
-            trackQueue.clear()
-        }
-
-        // enqueue
-        trackQueue.append(makeDict(track));
-
-        // play first URI
-        if (play) {
-            trackQueueClick(trackQueue.model.count - 1);
-        }
+        } else {
+            if (play) {
+                // clear play queue
+                player.mediaPlayer.playlist.clearWrapper()
+            }
+
+            // enqueue
+            player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(track.filename));
+
+            // play first URI
+            if (play) {
+                trackQueueClick(player.mediaPlayer.playlist.itemCount - 1);
+            }
+        }
+
     }
 
     function process(uri, play) {

=== modified file 'app/components/Helpers/UserMetricsHelper.qml'
--- app/components/Helpers/UserMetricsHelper.qml	2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UserMetricsHelper.qml	2016-01-15 18:03:59 +0000
@@ -36,21 +36,23 @@
     // Connections for usermetrics
     Connections {
         id: userMetricPlayerConnection
-        target: player
+        target: player.mediaPlayer
+
         property bool songCounted: false
 
-        onSourceChanged: {
-            songCounted = false
-        }
-
         onPositionChanged: {
             // Increment song count on Welcome screen if song has been
             // playing for over 10 seconds.
-            if (player.position > 10000 && !songCounted) {
+            if (player.mediaPlayer.position > 10000 && !songCounted) {
                 songCounted = true
                 songsMetric.increment()
                 console.debug("Increment UserMetrics")
             }
         }
     }
+
+    Connections {
+        target: player.mediaPlayer.playlist
+        onCurrentItemSourceChanged: userMetricPlayerConnection.songCounted = false
+    }
 }

=== modified file 'app/components/ListItemActions/AddToPlaylist.qml'
--- app/components/ListItemActions/AddToPlaylist.qml	2015-10-18 18:16:05 +0000
+++ app/components/ListItemActions/AddToPlaylist.qml	2016-01-15 18:03:59 +0000
@@ -25,10 +25,14 @@
     objectName: "addToPlaylistAction"
     text: i18n.tr("Add to playlist")
 
+    // Used when model can't be given to add to playlist
+    // for example in the Queue it is called metaModel not model
+    property var modelOverride: null
+
     onTriggered: {
         console.debug("Debug: Add track to playlist");
 
         mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
-                           {"chosenElements": [makeDict(model)]})
+                           {"chosenElements": [modelOverride || makeDict(model)]})
     }
 }

=== modified file 'app/components/ListItemActions/AddToQueue.qml'
--- app/components/ListItemActions/AddToQueue.qml	2015-08-12 23:36:44 +0000
+++ app/components/ListItemActions/AddToQueue.qml	2016-01-15 18:03:59 +0000
@@ -29,6 +29,6 @@
 
     onTriggered: {
         console.debug("Debug: Add track to queue: " + model)
-        trackQueue.append(model)
+        player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(model.filename))
     }
 }

=== modified file 'app/components/MusicToolbar.qml'
--- app/components/MusicToolbar.qml	2015-08-12 23:36:44 +0000
+++ app/components/MusicToolbar.qml	2016-01-15 18:03:59 +0000
@@ -18,7 +18,7 @@
  */
 
 import QtQuick 2.4
-import QtMultimedia 5.0
+import QtMultimedia 5.6
 import Ubuntu.Components 1.3
 
 Rectangle {
@@ -42,7 +42,7 @@
         anchors {
             fill: parent
         }
-        state: trackQueue.model.count === 0 ? "disabled" : "enabled"
+        state: player.mediaPlayer.playlist.empty ? "disabled" : "enabled"
         states: [
             State {
                 name: "disabled"
@@ -104,7 +104,7 @@
                 }
                 color: "#FFF"
                 height: units.gu(4)
-                name: player.playbackState === MediaPlayer.PlayingState ?
+                name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
                           "media-playback-pause" : "media-playback-start"
                 objectName: "disabledSmallPlayShape"
                 width: height
@@ -116,11 +116,10 @@
                     fill: parent
                 }
                 onClicked: {
-                    if (trackQueue.model.count === 0) {
+                    if (player.mediaPlayer.playlist.empty) {
                         playRandomSong();
-                    }
-                    else {
-                        player.toggle();
+                    } else {
+                        player.mediaPlayer.toggle();
                     }
                 }
             }
@@ -144,7 +143,7 @@
                      left: parent.left
                      top: parent.top
                  }
-                 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
+                 covers: [player.currentMeta]
                  size: parent.height
             }
 
@@ -170,8 +169,9 @@
                     elide: Text.ElideRight
                     fontSize: "small"
                     font.weight: Font.DemiBold
-                    text: player.currentMetaTitle === ""
-                          ? player.source : player.currentMetaTitle
+                    text: player.currentMeta.title === ""
+                          ? player.mediaPlayer.playlist.currentItemSource
+                          : player.currentMeta.title
                 }
 
                 /* Artist of track */
@@ -185,7 +185,7 @@
                     elide: Text.ElideRight
                     fontSize: "small"
                     opacity: 0.4
-                    text: player.currentMetaArtist
+                    text: player.currentMeta.author
                 }
             }
 
@@ -197,9 +197,9 @@
                     rightMargin: units.gu(3)
                     verticalCenter: parent.verticalCenter
                 }
-                color: "#FFF"
+                color: playerControlsPlayButtonMouseArea.pressed ? UbuntuColors.blue : "white"
                 height: units.gu(4)
-                name: player.playbackState === MediaPlayer.PlayingState ?
+                name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
                           "media-playback-pause" : "media-playback-start"
                 objectName: "playShape"
                 width: height
@@ -217,12 +217,13 @@
 
             /* Mouse area for the play button (ontop of the jump to now playing) */
             MouseArea {
+                id: playerControlsPlayButtonMouseArea
                 anchors {
                     bottom: parent.bottom
                     horizontalCenter: playerControlsPlayButton.horizontalCenter
                     top: parent.top
                 }
-                onClicked: player.toggle()
+                onClicked: player.mediaPlayer.toggle()
                 width: units.gu(8)
 
                 Rectangle {
@@ -260,16 +261,27 @@
                 }
                 color: UbuntuColors.blue
                 height: parent.height
-                width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
+                width: player.mediaPlayer.progress * playerControlsProgressBar.width
+
+                // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
+                // emit until it changes to the PLAYING state. But by asking for a
+                // value we get gst to perform a query and return a result
+                // However this has to be done once the source is set, hence the delay
+                //
+                // NOTE: This does not solve when the currentIndex is removed though
+                Timer {
+                    id: refreshProgressTimer
+                    interval: 48
+                    repeat: false
+                    onTriggered: playerControlsProgressBarHint.width = player.mediaPlayer.progress * playerControlsProgressBar.width
+                }
 
                 Connections {
-                    target: player
-                    onPositionChanged: {
-                        playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
-                    }
-                    onStopped: {
-                        playerControlsProgressBarHint.width = 0;
-                    }
+                    target: player.mediaPlayer.playlist
+                    // Call timer when source or index changes
+                    // so we call even if there are duplicate sources or source removal
+                    onCurrentItemSourceChanged: refreshProgressTimer.start()
+                    onCurrentIndexChanged: refreshProgressTimer.start()
                 }
             }
         }

=== modified file 'app/components/NowPlayingFullView.qml'
--- app/components/NowPlayingFullView.qml	2015-10-18 17:45:48 +0000
+++ app/components/NowPlayingFullView.qml	2016-01-15 18:03:59 +0000
@@ -51,7 +51,7 @@
             CoverGrid {
                 id: albumImage
                 anchors.centerIn: parent
-                covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
+                covers: [player.currentMeta]
                 size: parent.height
             }
         }
@@ -92,7 +92,15 @@
                 fontSize: "x-large"
                 maximumLineCount: 2
                 objectName: "playercontroltitle"
-                text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
+                text: {
+                    if (player.mediaPlayer.playlist.empty) {
+                        ""
+                    } else if (player.currentMeta.title === "") {
+                        player.mediaPlayer.playlist.currentSource
+                    } else {
+                        player.currentMeta.title
+                    }
+                }
                 wrapMode: Text.WordWrap
             }
 
@@ -108,7 +116,7 @@
                 color: styleMusic.nowPlaying.labelSecondaryColor
                 elide: Text.ElideRight
                 fontSize: "small"
-                text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
+                text: player.mediaPlayer.playlist.empty ? "" : player.currentMeta.author
             }
         }
 
@@ -122,12 +130,13 @@
 
             onReleased: {
                 var diff = mouse.x - lastX
+
                 if (Math.abs(diff) < units.gu(4)) {
                     return;
                 } else if (diff < 0) {
-                    player.nextSong()
+                    player.mediaPlayer.playlist.nextWrapper()
                 } else if (diff > 0) {
-                    player.previousSong()
+                    player.mediaPlayer.playlist.previousWrapper()
                 }
             }
         }
@@ -167,7 +176,7 @@
             fontSize: "small"
             height: parent.height
             horizontalAlignment: Text.AlignHCenter
-            text: durationToString(player.position)
+            text: durationToString(player.mediaPlayer.position)
             verticalAlignment: Text.AlignVCenter
             width: units.gu(3)
         }
@@ -176,10 +185,10 @@
             id: progressSliderMusic
             anchors.left: parent.left
             anchors.right: parent.right
-            maximumValue: player.duration  // load value at startup
+            maximumValue: player.mediaPlayer.duration || 1  // fallback to 1 when 0 so that the progress bar works
             objectName: "progressSliderShape"
             style: UbuntuBlueSliderStyle {}
-            value: player.position  // load value at startup
+            value: player.mediaPlayer.position  // load value at startup
 
             function formatValue(v) {
                 if (seeking) {  // update position label while dragging
@@ -194,7 +203,7 @@
 
             onSeekingChanged: {
                 if (seeking === false) {
-                    musicToolbarFullPositionLabel.text = durationToString(player.position)
+                    musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
                 }
             }
 
@@ -203,22 +212,23 @@
 
                 if (!pressed) {
                     seeked = true
-                    player.seek(value)
+                    player.mediaPlayer.seek(value)
 
                     musicToolbarFullPositionLabel.text = durationToString(value)
                 }
             }
 
             Connections {
-                target: player
+                target: player.mediaPlayer
                 onPositionChanged: {
                     // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
                     if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
-                        musicToolbarFullPositionLabel.text = durationToString(player.position)
-                        musicToolbarFullDurationLabel.text = durationToString(player.duration)
+                        musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position)
+                        musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration)
 
-                        progressSliderMusic.value = player.position
-                        progressSliderMusic.maximumValue = player.duration
+                        progressSliderMusic.value = player.mediaPlayer.position
+                        // fallback to 1 when 0 so that the progress bar works
+                        progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
                     }
 
                     progressSliderMusic.seeked = false;
@@ -240,9 +250,39 @@
             fontSize: "small"
             height: parent.height
             horizontalAlignment: Text.AlignHCenter
-            text: durationToString(player.duration)
+            text: durationToString(player.mediaPlayer.duration || 1)
             verticalAlignment: Text.AlignVCenter
             width: units.gu(3)
         }
+
+        // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
+        // emit until it changes to the PLAYING state. But by asking for a
+        // value we get gst to perform a query and return a result
+        // However this has to be done once the source is set, hence the delay
+        //
+        // NOTE: This does not solve when the currentIndex is removed though
+        Timer {
+            id: refreshProgressTimer
+            interval: 48
+            repeat: false
+            onTriggered: {
+                if (!progressSliderMusic.seeking) {
+                    musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position);
+                    musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration || 1);
+
+                    progressSliderMusic.value = player.mediaPlayer.position
+                    // fallback to 1 when 0 so that the progress bar works
+                    progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
+                }
+            }
+        }
+
+        Connections {
+            target: player.mediaPlayer.playlist
+            // Call timer when source or index changes
+            // so we call even if there are duplicate sources or source removal
+            onCurrentItemSourceChanged: refreshProgressTimer.start()
+            onCurrentIndexChanged: refreshProgressTimer.start()
+        }
     }
 }

=== modified file 'app/components/NowPlayingToolbar.qml'
--- app/components/NowPlayingToolbar.qml	2015-08-12 23:36:44 +0000
+++ app/components/NowPlayingToolbar.qml	2016-01-15 18:03:59 +0000
@@ -17,7 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-import QtMultimedia 5.0
+import QtMultimedia 5.6
 import QtQuick 2.4
 import Ubuntu.Components 1.3
 
@@ -37,7 +37,6 @@
         anchors.rightMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: player.repeat ? 1 : .4
         width: height
         onClicked: player.repeat = !player.repeat
 
@@ -47,23 +46,23 @@
             width: height
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
-            color: "white"
+            color: parent.pressed ? UbuntuColors.blue : "white"
             name: "media-playlist-repeat"
             objectName: "repeatShape"
-            opacity: player.repeat ? 1 : .4
+            opacity: player.repeat ? 1 : .2
         }
     }
 
     /* Previous button */
     MouseArea {
         id: nowPlayingPreviousButton
+        enabled: player.mediaPlayer.playlist.canGoPrevious
         anchors.right: nowPlayingPlayButton.left
         anchors.rightMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: trackQueue.model.count === 0  ? .4 : 1
         width: height
-        onClicked: player.previousSong()
+        onClicked: player.mediaPlayer.playlist.previousWrapper()
 
         Icon {
             id: nowPlayingPreviousIndicator
@@ -71,10 +70,10 @@
             width: height
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
-            color: "white"
+            color: parent.pressed ? UbuntuColors.blue : "white"
             name: "media-skip-backward"
             objectName: "previousShape"
-            opacity: 1
+            opacity: parent.enabled ? 1 : .2
         }
     }
 
@@ -84,7 +83,7 @@
         anchors.centerIn: parent
         height: units.gu(10)
         width: height
-        onClicked: player.toggle()
+        onClicked: player.mediaPlayer.toggle()
 
         Icon {
             id: nowPlayingPlayIndicator
@@ -92,8 +91,8 @@
             width: height
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
-            color: "white"
-            name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
+            color: parent.pressed ? UbuntuColors.blue : "white"
+            name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
             objectName: "playShape"
         }
     }
@@ -104,10 +103,10 @@
         anchors.left: nowPlayingPlayButton.right
         anchors.leftMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
+        enabled: player.mediaPlayer.playlist.canGoNext
         height: units.gu(6)
-        opacity: trackQueue.model.count === 0 ? .4 : 1
         width: height
-        onClicked: player.nextSong()
+        onClicked: player.mediaPlayer.playlist.nextWrapper()
 
         Icon {
             id: nowPlayingNextIndicator
@@ -115,10 +114,10 @@
             width: height
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
-            color: "white"
+            color: parent.pressed ? UbuntuColors.blue : "white"
             name: "media-skip-forward"
             objectName: "forwardShape"
-            opacity: 1
+            opacity: parent.enabled ? 1 : .2
         }
     }
 
@@ -129,7 +128,6 @@
         anchors.leftMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: player.shuffle ? 1 : .4
         width: height
         onClicked: player.shuffle = !player.shuffle
 
@@ -139,10 +137,10 @@
             width: height
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
-            color: "white"
+            color: parent.pressed ? UbuntuColors.blue : "white"
             name: "media-playlist-shuffle"
             objectName: "shuffleShape"
-            opacity: player.shuffle ? 1 : .4
+            opacity: player.shuffle ? 1 : .2
         }
     }
 
@@ -166,17 +164,7 @@
             }
             color: UbuntuColors.blue
             height: parent.height
-            width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
-
-            Connections {
-                target: player
-                onPositionChanged: {
-                    playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
-                }
-                onStopped: {
-                    playerControlsProgressBarHint.width = 0;
-                }
-            }
+            width: player.mediaPlayer.progress * playerControlsProgressBar.width
         }
     }
 }

=== added file 'app/components/Player.qml'
--- app/components/Player.qml	1970-01-01 00:00:00 +0000
+++ app/components/Player.qml	2016-01-15 18:03:59 +0000
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtMultimedia 5.6
+import QtQuick 2.4
+import Qt.labs.settings 1.0
+
+import QtQuick.LocalStorage 2.0
+import "../logic/meta-database.js" as Library
+
+Item {
+    objectName: "player"
+
+    // For autopilot as we can't access the MediaPlayer object pad.lv/1269578
+    readonly property bool isPlaying: mediaPlayerObject.playbackState === MediaPlayer.PlayingState
+    readonly property alias count: mediaPlayerPlaylist.itemCount
+    readonly property alias currentIndex: mediaPlayerPlaylist.currentIndex
+    readonly property alias currentItemSource: mediaPlayerPlaylist.currentItemSource
+    readonly property alias position: mediaPlayerObject.position
+
+    // FIXME: pad.lv/1269578 use Item as autopilot cannot 'see' a var/QtObject
+    property alias currentMeta: currentMetaItem
+
+    property alias mediaPlayer: mediaPlayerObject
+    property alias repeat: settings.repeat
+    property alias shuffle: settings.shuffle
+
+    Item {
+        id: currentMetaItem
+        objectName: "currentMeta"
+
+        property string album: ""
+        property string art: ""
+        property string author: ""
+        property string filename: ""
+        property string title: ""
+    }
+
+    // Return the metadata for the source given from mediascanner2
+    function metaForSource(source) {
+        var blankMeta = {
+            album: "",
+            art: "",
+            author: "",
+            filename: "",
+            title: ""
+        };
+
+        source = source.toString();
+
+        if (source.indexOf("file://") === 0) {
+            source = source.substring(7);
+        }
+
+        return musicStore.lookup(decodeFileURI(source)) || blankMeta;
+    }
+
+    Settings {
+        id: settings
+        category: "PlayerSettings"
+
+        property bool repeat: true
+        property bool shuffle: false
+    }
+
+    MediaPlayer {
+        id: mediaPlayerObject
+        playlist: Playlist {
+            id: mediaPlayerPlaylist
+            playbackMode: {
+                if (settings.shuffle) {
+                    Playlist.Random
+                } else if (settings.repeat) {
+                    Playlist.Loop
+                } else {
+                    Playlist.Sequential
+                }
+            }
+
+            // as that doesn't emit changes
+            readonly property bool canGoPrevious: {  // FIXME: pad.lv/1517580 use previousIndex() > -1 after mh implements it
+                currentIndex !== 0 ||
+                settings.repeat ||
+                settings.shuffle ||  // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
+                mediaPlayerObject.position > 5000
+            }
+            readonly property bool canGoNext: {  // FIXME: pad.lv/1517580 use nextIndex() > -1 after mh implements it
+                currentIndex !== (itemCount - 1) ||
+                settings.repeat ||
+                settings.shuffle  // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
+            }
+            readonly property int count: itemCount  // header actions etc depend on the model having 'count'
+            readonly property bool empty: itemCount === 0
+            property int pendingCurrentIndex: -1
+            property var pendingCurrentState: null
+            property int pendingShuffle: -1
+
+            onCurrentItemSourceChanged: {
+                var meta = metaForSource(currentItemSource);
+
+                currentMeta.album = meta.album;
+                currentMeta.art = meta.art;
+                currentMeta.author = meta.author;
+                currentMeta.filename = meta.filename;
+                currentMeta.title = meta.title;
+
+                mediaPlayerObject._calcProgress();
+            }
+            onItemChanged: {
+                console.debug("*** Saving play queue in onItemChanged");
+                saveQueue()
+            }
+            onItemInserted: {
+                // When add to queue is done on an empty list currentIndex needs to be set
+                if (start === 0 && currentIndex === -1 && pendingCurrentIndex < 1 && pendingShuffle === -1) {
+                    currentIndex = 0;
+
+                    pendingCurrentIndex = -1;
+                    processPendingCurrentState();
+                }
+
+                // Check if the pendingCurrentIndex is now valid
+                if (pendingCurrentIndex !== -1 && pendingCurrentIndex < itemCount) {
+                    currentIndex = pendingCurrentIndex;
+
+                    pendingCurrentIndex = -1;
+                    processPendingCurrentState();
+                }
+
+                // Check if there is pending shuffle
+                // pendingShuffle holds the expected size of the model
+                if (pendingShuffle > -1 && pendingShuffle <= itemCount) {
+                    pendingShuffle = -1;
+
+                    nextWrapper();  // find a random track
+                    mediaPlayerObject.play();  // next does not enforce play
+                }
+
+                console.debug("*** Saving play queue in onItemInserted");
+                saveQueue()
+            }
+            onItemRemoved: {
+                console.debug("*** Saving play queue in onItemRemoved");
+                saveQueue()
+            }
+
+            function addItemsFromModel(model) {
+                var items = []
+
+                for (var i=0; i < model.rowCount; i++) {
+                    items.push(Qt.resolvedUrl(model.get(i, model.RoleModelData).filename));
+                }
+
+                addItems(items);
+            }
+
+            // Wrap the clear() method because we need to call stop first
+            function clearWrapper() {
+                // Stop the current playback (this ensures that play is run later)
+                if (mediaPlayerObject.playbackState === MediaPlayer.PlayingState) {
+                    mediaPlayerObject.stop();
+                }
+
+                clear();
+            }
+
+            // Replicates a model.get() on a ms2 model
+            function get(index, role) {
+                return metaForSource(itemSource(index));
+            }
+
+            // Wrap the next() method so we can check canGoNext
+            function nextWrapper() {
+                if (canGoNext) {
+                    next();
+                }
+            }
+
+            // Wrap the previous() method so we can check canGoPrevious
+            function previousWrapper() {
+                if (canGoPrevious) {
+                    previous();
+                }
+            }
+
+            // Process the pending current PlaybackState
+            function processPendingCurrentState() {
+                if (pendingCurrentState === MediaPlayer.PlayingState) {
+                    console.debug("Loading pending state play()");
+                    mediaPlayerObject.play();
+                } else if (pendingCurrentState === MediaPlayer.PausedState) {
+                    console.debug("Loading pending state pause()");
+                    mediaPlayerObject.pause();
+                } else if (pendingCurrentState === MediaPlayer.StoppedState) {
+                    console.debug("Loading pending state stop()");
+                    mediaPlayerObject.stop();
+                }
+
+                pendingCurrentState = null;
+            }
+
+            // Wrapper for removeItems(from, to) so that we can use removeItems(list) until it is implemented upstream
+            function removeItemsWrapper(items) {
+                var previous = -1, end = -1;
+
+                // Sort indexes backwards so we don't have to deal with offsets when removing
+                items.sort(function(a,b) { return b-a; });
+
+                console.debug("To Remove", JSON.stringify(items));
+
+                // Merge ranges of indexes into sets of start, end points
+                // and call removeItems as we go along
+                for (var i=0; i < items.length; i++) {
+                    if (end == -1) {  // first value found set to first
+                        end = items[i];
+                    } else if (previous - 1 !== items[i]) {  // set has ended (next is not 1 lower)
+                        console.debug("RemoveItems", previous, end);
+                        player.mediaPlayer.playlist.removeItems(previous, end);
+
+                        end = items[i];  // set new high value for the next set
+                    }
+
+                    previous = items[i];  // last value to check if next is 1 lower
+                }
+
+                // Remove last set in list as well
+                if (items.length > 0) {
+                    console.debug("RemoveItems", items[items.length - 1], end);
+                    player.mediaPlayer.playlist.removeItems(items[items.length - 1], end);
+                }
+            }
+
+            function saveQueue(start, end) {
+                // FIXME: load and save do not work yet pad.lv/1510225
+                // so use our localstorage method for now
+                // save("/home/phablet/.local/share/com.ubuntu.music/queue.m3u");
+                if (mainView.loadedUI) {
+                    // Don't be intelligent, just clear and rebuild the queue for now
+                    Library.clearQueue();
+
+                    var sources = [];
+
+                    for (var i=0; i < mediaPlayerPlaylist.itemCount; i++) {
+                        sources.push(mediaPlayerPlaylist.itemSource(i));
+                    }
+
+                    if (sources.length > 0) {
+                        Library.addQueueList(sources);
+                    }
+                }
+            }
+
+            function setCurrentIndex(index) {
+                // Set the currentIndex but if the itemCount is too low then wait
+                if (index < mediaPlayerPlaylist.itemCount) {
+                    mediaPlayerPlaylist.currentIndex = index;
+                } else {
+                    pendingCurrentIndex = index;
+                }
+            }
+
+            function setPendingCurrentState(pendingState) {
+                // Set the PlaybackState to set once pendingCurrentIndex is set
+                pendingCurrentState = pendingState;
+
+                if (pendingCurrentIndex === -1) {
+                    processPendingCurrentState();
+                }
+            }
+
+            function setPendingShuffle(modelSize) {
+                // Run next() and play() when the modelSize is reached
+                if (modelSize <= itemCount) {
+                    mediaPlayerPlaylist.nextWrapper();  // find a random track
+                    mediaPlayerObject.play();  // next does not enforce play
+                } else {
+                    pendingShuffle = modelSize;
+                }
+            }
+        }
+
+        property bool endOfMedia: false
+        property double progress: 0
+
+        onDurationChanged: _calcProgress()
+        onPositionChanged: _calcProgress()
+
+        onStatusChanged: {
+            if (status == MediaPlayer.EndOfMedia && !settings.repeat) {
+                console.debug("End of media, stopping.")
+
+                // Tells the onStopped to set the curentIndex = 0
+                endOfMedia = true;
+
+                stop();
+            }
+        }
+
+        onStopped: {  // hit when pressing next() on last track with repeat off
+            console.debug("onStopped.")
+
+            // FIXME: Workaround for pad.lv/1494031 in the stopped state
+            // we do not get position/duration info so if there are items in
+            // the queue and we have stopped instead pause
+            if (playlist.itemCount > 0) {
+                // We have just ended media so jump to start of playlist
+                if (endOfMedia) {
+                    playlist.currentIndex = 0;
+
+                    // Play then pause otherwise when we come from EndOfMedia
+                    // if calls next() until EndOfMedia again
+                    play();
+                }
+
+                pause();
+            }
+
+            endOfMedia = false;  // always reset endOfMedia
+            _calcProgress();  // ensures progress bar has reset
+        }
+
+        function _calcProgress() {
+            if (duration > 0) {
+                progress = position / duration;
+            } else if (position >= duration) {
+                progress = 0;
+            } else {
+                progress = 0;
+            }
+        }
+
+        function toggle() {
+            if (playbackState === MediaPlayer.PlayingState) {
+                pause();
+            } else {
+                play();
+            }
+        }
+    }
+}

=== removed file 'app/components/Player.qml'
--- app/components/Player.qml	2015-06-28 03:06:49 +0000
+++ app/components/Player.qml	1970-01-01 00:00:00 +0000
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2013, 2014, 2015
- *      Andrew Hayzen <ahayzen@xxxxxxxxx>
- *      Daniel Holm <d.holmen@xxxxxxxxx>
- *      Victor Thompson <victor.thompson@xxxxxxxxx>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.4
-import QtMultimedia 5.0
-import QtQuick.LocalStorage 2.0
-import Qt.labs.settings 1.0
-
-/*
- * This file should *only* manage the media playing and the relevant settings
- * It should therefore only access MediaPlayer, trackQueue and Settings
- * Anything else within the app should use Connections to listen for changes
- */
-
-
-Item {
-    objectName: "player"
-
-    property string currentMetaAlbum: ""
-    property string currentMetaArt: ""
-    property string currentMetaArtist: ""
-    property string currentMetaFile: ""
-    property string currentMetaTitle: ""
-    property int currentIndex: -1
-    property int duration: 1
-    readonly property bool isPlaying: player.playbackState === MediaPlayer.PlayingState
-    readonly property var playbackState: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.playbackState : MediaPlayer.StoppedState
-    property int position: 0
-    property alias repeat: settings.repeat
-    property alias shuffle: settings.shuffle
-    readonly property string source: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.source : ""
-    readonly property double volume: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.volume : 1.0
-
-    signal stopped()
-
-    Settings {
-        id: settings
-        category: "PlayerSettings"
-
-        property bool repeat: true
-        property bool shuffle: false
-    }
-
-    Connections {
-        target: trackQueue.model
-        onCountChanged: {
-            if (trackQueue.model.count === 1 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
-                player.currentIndex = 0;
-                setSource(Qt.resolvedUrl(trackQueue.model.get(0).filename))
-            } else if (trackQueue.model.count === 0 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
-                player.currentIndex = -1
-                setSource("")
-            }
-        }
-    }
-
-    function getSong(direction, startPlaying, fromControls) {
-        // Seek to start if threshold reached when selecting previous
-        if (direction === -1 && (player.position / 1000) > 5)
-        {
-            player.seek(0);  // seek to start
-            return;
-        }
-
-        if (trackQueue.model.count == 0)
-        {
-            customdebug("No tracks in queue.");
-            return;
-        }
-
-        // default fromControls and startPlaying to true
-        fromControls = fromControls === undefined ? true : fromControls;
-        startPlaying = startPlaying === undefined ? true : startPlaying;
-        var newIndex;
-
-        console.log("currentIndex: " + currentIndex)
-        console.log("trackQueue.count: " + trackQueue.model.count)
-
-        // Do not shuffle if repeat is off and there is only one track in the queue
-        if (shuffle && !(trackQueue.model.count === 1 && !repeat)) {
-            var now = new Date();
-            var seed = now.getSeconds();
-
-            // trackQueue must be above 1 otherwise an infinite loop will occur
-            do {
-                newIndex = (Math.floor((trackQueue.model.count)
-                                       * Math.random(seed)));
-            } while (newIndex === currentIndex && trackQueue.model.count > 1)
-        } else {
-            if ((currentIndex < trackQueue.model.count - 1 && direction === 1 )
-                    || (currentIndex > 0 && direction === -1)) {
-                newIndex = currentIndex + direction
-            } else if(direction === 1 && (repeat || fromControls)) {
-                newIndex = 0
-            } else if(direction === -1 && (repeat || fromControls)) {
-                newIndex = trackQueue.model.count - 1
-            }
-            else
-            {
-                player.stop()
-                return;
-            }
-        }
-
-        if (startPlaying) {  // only start the track if told
-            playSong(trackQueue.model.get(newIndex).filename, newIndex)
-        }
-        else {
-            currentIndex = newIndex
-            setSource(Qt.resolvedUrl(trackQueue.model.get(newIndex).filename))
-        }
-
-        // Set index into queue
-        queueIndex = currentIndex
-    }
-
-    function nextSong(startPlaying, fromControls) {
-        getSong(1, startPlaying, fromControls)
-    }
-
-    function pause() {
-        mediaPlayerLoader.item.pause();
-    }
-
-    function play() {
-        mediaPlayerLoader.item.play();
-    }
-
-    function playSong(filepath, index) {
-        stop();
-        currentIndex = index;
-        queueIndex = index;
-        setSource(filepath);
-        play();
-    }
-
-    function previousSong(startPlaying) {
-        getSong(-1, startPlaying)
-    }
-
-    function seek(position) {
-        mediaPlayerLoader.item.seek(position);
-    }
-
-    function setSource(filepath) {
-        mediaPlayerLoader.item.source = Qt.resolvedUrl(filepath);
-    }
-
-    function setVolume(volume) {
-        mediaPlayerLoader.item.volume = volume
-    }
-
-    function stop() {
-        mediaPlayerLoader.item.stop();
-    }
-
-    function toggle() {
-        if (player.playbackState == MediaPlayer.PlayingState) {
-            pause()
-        }
-        else {
-            play()
-        }
-    }
-
-    Loader {
-        id: mediaPlayerLoader
-        asynchronous: true
-        sourceComponent: Component {
-            MediaPlayer {
-                muted: false
-
-                onDurationChanged: player.duration = duration
-                onPositionChanged: player.position = position
-
-                onSourceChanged: {
-                    // Force invalid source to ""
-                    if (source === undefined || source === false) {
-                        source = ""
-                        return
-                    }
-
-                    if (source.toString() === "") {
-                        player.currentIndex = -1
-                        player.stop()
-                    }
-                    else {
-                        var obj;
-
-                        if (source.toString().indexOf("file://") === 0) {
-                            obj = musicStore.lookup(decodeFileURI(source.toString().substring(7)))
-                        } else {
-                            obj = musicStore.lookup(decodeFileURI(source.toString()))
-                        }
-
-                        // protect against null reponse from the lookup
-                        if (obj !== null) {
-                            // protect against undefined properties
-                            player.currentMetaAlbum = obj.album || "";
-                            player.currentMetaArt = obj.art || "";
-                            player.currentMetaArtist = obj.author || "";
-                            player.currentMetaFile = obj.filename || "";
-                            player.currentMetaTitle = obj.title || "";
-                        } else {
-                            console.debug("Mediascanner lookup resulted in null object", source.toString())
-                        }
-                    }
-
-                    console.log("Source: " + source.toString())
-                    console.log("Index: " + player.currentIndex)
-                }
-
-                onStatusChanged: {
-                    if (status == MediaPlayer.EndOfMedia) {
-                        nextSong(true, false) // next track
-                    }
-                }
-
-                onStopped: player.stopped()
-            }
-        }
-    }
-}
-

=== modified file 'app/components/Queue.qml'
--- app/components/Queue.qml	2016-01-09 12:49:43 +0000
+++ app/components/Queue.qml	2016-01-15 18:03:59 +0000
@@ -31,39 +31,43 @@
     anchors {
         fill: parent
     }
+    autoModelMove: false  // ensures we use moveItem() not move() in onReorder
     footer: Item {
         height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
     }
-    model: trackQueue.model
+    model: player.mediaPlayer.playlist
     objectName: "nowPlayingqueueList"
 
     onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
 
     delegate: MusicListItem {
         id: queueListItem
-        color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
+        color: player.mediaPlayer.playlist.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
+        height: units.gu(7)
         leadingActions: ListItemActions {
             actions: [
                 Remove {
-                    onTriggered: trackQueue.removeQueueList([index])
+                    onTriggered: player.mediaPlayer.playlist.removeItem(index)
                 }
             ]
         }
         multiselectable: true
         objectName: "nowPlayingListItem" + index
+        state: ""
         reorderable: true
         subtitle {
             objectName: "artistLabel"
-            text: model.author
+            text: metaModel.author
         }
         title {
-            color: player.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
+            color: player.mediaPlayer.playlist.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
             objectName: "titleLabel"
-            text: model.title
+            text: metaModel.title
         }
         trailingActions: ListItemActions {
             actions: [
                 AddToPlaylist {
+                    modelOverride: metaModel  // model is not exposed with metadata so use metaModel
                 }
             ]
             delegate: ActionDelegate {
@@ -71,24 +75,18 @@
             }
         }
 
+        property var metaModel: player.metaForSource(model.source)
+
         onItemClicked: {
-            customdebug("File: " + model.filename) // debugger
-            trackQueueClick(index);  // toggle track state
+            customdebug("File: " + model.source) // debugger
+            trackQueueClick(index);
         }
     }
 
+
     onReorder: {
-        Library.moveQueueItem(from, to);
-
-        // Maintain currentIndex with current song
-        if (from === player.currentIndex) {
-            player.currentIndex = to;
-        } else if (from < player.currentIndex && to >= player.currentIndex) {
-            player.currentIndex -= 1;
-        } else if (from > player.currentIndex && to <= player.currentIndex) {
-            player.currentIndex += 1;
-        }
-
-        queueIndex = player.currentIndex
+        console.debug("Move: ", from, to);
+
+        player.mediaPlayer.playlist.moveItem(from, to);
     }
 }

=== modified file 'app/components/ViewButton/QueueAllButton.qml'
--- app/components/ViewButton/QueueAllButton.qml	2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/QueueAllButton.qml	2016-01-15 18:03:59 +0000
@@ -28,7 +28,7 @@
 
     property var model
 
-    onClicked: addQueueFromModel(model)
+    onClicked: player.mediaPlayer.playlist.addItemsFromModel(model)
 
     Text {
         anchors {

=== modified file 'app/components/ViewButton/ShuffleButton.qml'
--- app/components/ViewButton/ShuffleButton.qml	2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/ShuffleButton.qml	2016-01-15 18:03:59 +0000
@@ -28,7 +28,7 @@
 
     property var model
 
-    onClicked: shuffleModel(model)
+    onClicked: playRandomSong(model)
 
     Text {
         anchors {

=== modified file 'app/logic/meta-database.js'
--- app/logic/meta-database.js	2015-06-20 18:01:59 +0000
+++ app/logic/meta-database.js	2016-01-15 18:03:59 +0000
@@ -70,7 +70,7 @@
         var ind = getNextIndex(tx);
 
         for (var i = 0; i < items.length; i++) {
-            tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i].filename]);
+            tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i]]);
         }
     }
     );
@@ -156,8 +156,15 @@
     db.transaction( function(tx) {
         var rs = tx.executeSql("SELECT * FROM queue ORDER BY ind ASC");
         for(var i = 0; i < rs.rows.length; i++) {
-            if (musicStore.lookup(decodeFileURI(rs.rows.item(i).filename)) != null) {
-                res.push(makeDict(musicStore.lookup(decodeFileURI(rs.rows.item(i).filename))));
+            var filename = rs.rows.item(i).filename;
+
+            // ms2 doesn't expect the URI scheme so strip file://
+            if (filename.indexOf("file://") == 0) {
+                filename = filename.substr(7);
+            }
+
+            if (musicStore.lookup(decodeFileURI(filename)) != null) {
+                res.push(Qt.resolvedUrl(filename));
             }
         }
     });

=== modified file 'app/logic/playlists.js'
--- app/logic/playlists.js	2015-06-20 17:57:58 +0000
+++ app/logic/playlists.js	2016-01-15 18:03:59 +0000
@@ -230,6 +230,11 @@
             for (j = 0; j < rs.rows.length; j++) {
                 var dbItem = rs.rows.item(j)
 
+                // ms2 doesn't expect the URI scheme so strip file://
+                if (dbItem.filename.indexOf("file://") === 0) {
+                    dbItem.filename = dbItem.filename.substr(7);
+                }
+
                 if (musicStore.lookup(decodeFileURI(dbItem.filename)) === null) {
                     erroneousTracks.push(dbItem.i);
                 } else {

=== modified file 'app/music-app.qml'
--- app/music-app.qml	2015-12-03 14:14:14 +0000
+++ app/music-app.qml	2016-01-15 18:03:59 +0000
@@ -22,7 +22,7 @@
 import Ubuntu.Components.Popups 1.3
 import Ubuntu.MediaScanner 0.1
 import Qt.labs.settings 1.0
-import QtMultimedia 5.0
+import QtMultimedia 5.6
 import QtQuick.LocalStorage 2.0
 import QtGraphicalEffects 1.0
 import "logic/stored-request.js" as StoredRequest
@@ -56,6 +56,11 @@
         }
     }
 
+    Connections {  // save the current queueIndex for when the app restarts
+        target: player.mediaPlayer.playlist
+        onCurrentIndexChanged: startupSettings.queueIndex = player.mediaPlayer.playlist.currentIndex
+    }
+
     // Global keyboard shortcuts
     focus: true
     Keys.onPressed: {
@@ -73,33 +78,33 @@
 
             switch (event.key) {
             case Qt.Key_Right:  //  Alt+Right   Seek forward +10secs
-                position = player.position + 10000 < player.duration
-                        ? player.position + 10000 : player.duration;
-                player.seek(position);
+                position = player.mediaPlayer.position + 10000 < player.mediaPlayer.duration
+                        ? player.mediaPlayer.position + 10000 : player.mediaPlayer.duration;
+                player.mediaPlayer.seek(position);
                 break;
             case Qt.Key_Left:  //   Alt+Left    Seek backwards -10secs
-                position = player.position - 10000 > 0
-                        ? player.position - 10000 : 0;
-                player.seek(position);
+                position = player.mediaPlayer.position - 10000 > 0
+                        ? player.mediaPlayer.position - 10000 : 0;
+                player.mediaPlayer.seek(position);
                 break;
             }
         }
         else if(event.modifiers === Qt.ControlModifier) {
             switch (event.key) {
             case Qt.Key_Left:   //  Ctrl+Left   Previous Song
-                player.previousSong(true);
+                player.mediaPlayer.playlist.previousWrapper();
                 break;
             case Qt.Key_Right:  //  Ctrl+Right  Next Song
-                player.nextSong(true, true);
+                player.mediaPlayer.playlist.nextWrapper();
                 break;
             case Qt.Key_Up:  //     Ctrl+Up     Volume up
-                player.volume = player.volume + .1 > 1 ? 1 : player.volume + .1
+                player.mediaPlayer.volume = player.mediaPlayer.volume + .1 > 1 ? 1 : player.mediaPlayer.volume + .1
                 break;
             case Qt.Key_Down:  //   Ctrl+Down   Volume down
-                player.volume = player.volume - .1 < 0 ? 0 : player.volume - .1
+                player.mediaPlayer.volume = player.mediaPlayer.volume - .1 < 0 ? 0 : player.mediaPlayer.volume - .1
                 break;
             case Qt.Key_R:  //      Ctrl+R      Repeat toggle
-                player.repeat = !player.repeat
+                player.mediaPlayer.repeat = !player.mediaPlayer.repeat
                 break;
             case Qt.Key_F:  //      Ctrl+F      Show Search popup
                 if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
@@ -116,13 +121,13 @@
                 tabs.pushNowPlaying()
                 break;
             case Qt.Key_P:  //      Ctrl+P      Toggle playing state
-                player.toggle();
+                player.mediaPlayer.toggle();
                 break;
             case Qt.Key_Q:  //      Ctrl+Q      Quit the app
                 Qt.quit();
                 break;
             case Qt.Key_U:  //      Ctrl+U      Shuffle toggle
-                player.shuffle = !player.shuffle
+                player.mediaPlayer.shuffle = !player.mediaPlayer.shuffle
                 break;
             }
         }
@@ -152,15 +157,15 @@
         id: nextAction
         text: i18n.tr("Next")
         keywords: i18n.tr("Next Track")
-        onTriggered: player.nextSong()
+        onTriggered: player.mediaPlayer.playlist.nextWrapper()
     }
     Action {
         id: playsAction
-        text: player.playbackState === MediaPlayer.PlayingState ?
-                  i18n.tr("Pause") : i18n.tr("Play")
-        keywords: player.playbackState === MediaPlayer.PlayingState ?
-                      i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
-        onTriggered: player.toggle()
+        text: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
+              ? i18n.tr("Pause") : i18n.tr("Play")
+        keywords: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
+                  ? i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
+        onTriggered: player.mediaPlayer.toggle()
     }
     Action {
         id: backAction
@@ -175,13 +180,13 @@
         id: prevAction
         text: i18n.tr("Previous")
         keywords: i18n.tr("Previous Track")
-        onTriggered: player.previousSong()
+        onTriggered: player.mediaPlayer.playlist.previousWrapper()
     }
     Action {
         id: stopAction
         text: i18n.tr("Stop")
         keywords: i18n.tr("Stop Playback")
-        onTriggered: player.stop()
+        onTriggered: player.mediaPlayer.stop()
     }
 
     actions: [nextAction, playsAction, prevAction, stopAction, backAction]
@@ -203,23 +208,6 @@
     width: units.gu(100)
     height: units.gu(80)
 
-    WorkerModelLoader {
-        id: queueLoaderWorker
-        canLoad: false
-        model: trackQueue.model
-        syncFactor: 10
-
-        onPreLoadCompleteChanged: {
-            if (preLoadComplete) {
-                player.currentIndex = queueIndex
-
-                if (list[queueIndex] !== undefined) {
-                    player.setSource(list[queueIndex].filename)
-                }
-            }
-        }
-    }
-
     WorkerWaiter {
         id: waitForWorker
     }
@@ -229,14 +217,29 @@
         customdebug("Version "+appVersion) // print the curren version
 
         Library.createRecent()  // initialize recent
+        Library.createQueue()  // create queue if it doesn't exist
 
         // initialize playlists
         Playlists.initializePlaylist()
 
         if (!args.values.url) {
-            // allow the queue loader to start
-            queueLoaderWorker.canLoad = !Library.isQueueEmpty()
-            queueLoaderWorker.list = Library.getQueue()
+            // load the previous queue as there are no args
+
+            // FIXME: load and save do not work yet pad.lv/1510225
+            // so use our localstorage method for now
+            // player.mediaPlayer.playlist.load("/home/phablet/.local/share/com.ubuntu.music/queue.m3u")
+            // use onloaded() and onLoadFailed() to confirm it is complete
+
+            if (!Library.isQueueEmpty()) {
+                console.debug("*** Restoring library queue");
+                player.mediaPlayer.playlist.addItems(Library.getQueue());
+
+                player.mediaPlayer.playlist.setCurrentIndex(queueIndex);
+                player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PausedState);
+            }
+            else {
+                console.debug("Queue is empty, not loading any recent tracks");
+            }
         }
 
         // everything else
@@ -301,23 +304,6 @@
         }
     }
 
-    function addQueueFromModel(model)
-    {
-        // TODO: remove once playlists uses U1DB
-        if (model.hasOwnProperty("linkLibraryListModel")) {
-            model = model.linkLibraryListModel;
-        }
-
-        var items = []
-
-        for (var i=0; i < model.rowCount; i++) {
-            items.push(model.get(i, model.RoleModelData))
-        }
-
-        // Add model to queue storage
-        trackQueue.appendList(items)
-    }
-
     // Converts an duration in ms to a formated string ("minutes:seconds")
     function durationToString(duration) {
         var minutes = Math.floor((duration/1000) / 60);
@@ -336,20 +322,13 @@
             album: model.album,
             art: model.art,
             author: model.author,
-            filename: model.filename,
+            filename: model.filename || model.source,
             title: model.title
         };
     }
 
-    function trackClicked(model, index, play, clear) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, trackClicked, [model, index, play, clear])
-            return;
-        }
-
+    // Clear the queue, queue this model and play the specific index
+    function trackClicked(model, index, play) {
         // TODO: remove once playlists uses U1DB
         if (model.hasOwnProperty("linkLibraryListModel")) {
             model = model.linkLibraryListModel;
@@ -358,64 +337,50 @@
         var file = Qt.resolvedUrl(model.get(index, model.RoleModelData).filename);
 
         play = play === undefined ? true : play  // default play to true
-        clear = clear === undefined ? false : clear  // force clear and will ignore player.toggle()
-
-        if (!clear) {
-            // If same track and on Now playing page then toggle
-            if (mainPageStack.currentPage.title === i18n.tr("Now playing")
-                    && trackQueue.model.get(player.currentIndex) !== undefined
-                    && Qt.resolvedUrl(trackQueue.model.get(player.currentIndex).filename) === file) {
-                player.toggle()
-                return;
-            }
-        }
-
-        trackQueue.clear();  // clear the old model
-
-        addQueueFromModel(model);
+
+        player.mediaPlayer.playlist.clearWrapper();  // clear the old model
+        player.mediaPlayer.playlist.setCurrentIndex(index);
+        player.mediaPlayer.playlist.addItemsFromModel(model);
 
         if (play) {
-            player.playSong(file, index);
+            // Set the pending state for the playlist
+            // this will be set once the currentIndex has been appened to the playlist
+            player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
 
             // Show the Now playing page and make sure the track is visible
             tabs.pushNowPlaying();
         }
-        else {
-            player.setSource(file);
-        }
     }
 
+    // Play or pause a current track in the queue
+    // - the index has been tapped by the user
     function trackQueueClick(index) {
-        if (player.currentIndex === index) {
-            player.toggle();
-        }
-        else {
-            player.playSong(trackQueue.model.get(index).filename, index);
-        }
-    }
-
-    function playRandomSong(shuffle)
-    {
-        trackQueue.clear();
-
-        var now = new Date();
-        var seed = now.getSeconds();
-        var index = Math.floor(allSongsModel.rowCount * Math.random(seed));
-
-        player.shuffle = shuffle === undefined ? true : shuffle;
-
-        trackClicked(allSongsModel, index, true)
-    }
-
-    function shuffleModel(model)
-    {
-        var now = new Date();
-        var seed = now.getSeconds();
-        var index = Math.floor(model.count * Math.random(seed));
-
+        if (player.mediaPlayer.playlist.currentIndex === index) {
+            player.mediaPlayer.toggle();
+        }  else {
+            player.mediaPlayer.playlist.setCurrentIndex(index);
+            player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
+        }
+    }
+
+    // Clear the queue and play a random track from this model
+    // - user has selected "Shuffle" in album/artists or "Tap to play random"
+    function playRandomSong(model)
+    {
+        // If no model is given use all the tracks
+        if (model === undefined) {
+            model = allSongsModel;
+        }
+
+        player.mediaPlayer.playlist.clearWrapper();
+        player.mediaPlayer.playlist.addItemsFromModel(model);
         player.shuffle = true;
 
-        trackClicked(model, index, true)
+        // Once the model count has been reached in the queue
+        // shuffle the model
+        player.mediaPlayer.playlist.setPendingShuffle(model.count);
+
+        tabs.pushNowPlaying();
     }
 
     // Wrapper function around decodeURIComponent() to prevent exceptions
@@ -453,8 +418,15 @@
                 var removed = []
 
                 // Find tracks from the queue that aren't in ms2 anymore
-                for (i=0; i < trackQueue.model.count; i++) {
-                    if (musicStore.lookup(decodeFileURI(trackQueue.model.get(i).filename)) === null) {
+                for (i=0; i < player.mediaPlayer.playlist.count; i++) {
+                    var file = decodeFileURI(player.mediaPlayer.playlist.itemSource(i));
+
+                    // ms2 doesn't expect the URI scheme so strip file://
+                    if (file.indexOf("file://") === 0) {
+                        file = file.substr(7);
+                    }
+
+                    if (musicStore.lookup(file) === null) {
                         removed.push(i)
                     }
                 }
@@ -462,7 +434,7 @@
                 // If there are removed tracks then remove them from the queue and store
                 if (removed.length > 0) {
                     console.debug("Removed queue:", JSON.stringify(removed))
-                    trackQueue.removeQueueList(removed)
+                    player.mediaPlayer.playlist.removeItemsWrapper(removed.slice());
                 }
 
                 // Loop through playlists, getPlaylistTracks will remove any tracks that don't exist
@@ -529,7 +501,7 @@
             if (status === SongsModel.Ready) {
                 // Play album it tracks exist
                 if (rowCount > 0 && selectedAlbum) {
-                    trackClicked(songsAlbumArtistModel, 0, true, true);
+                    trackClicked(songsAlbumArtistModel, 0, true);
 
                     // Add album to recent list
                     Library.addRecent(songsAlbumArtistModel.get(0, SongsModel.RoleModelData).album, "album")
@@ -579,105 +551,6 @@
         }
     }
 
-    // list of tracks on startup. This is just during development
-    LibraryListModel {
-        id: trackQueue
-        objectName: "trackQueue"
-
-        function append(listElement)
-        {
-            model.append(makeDict(listElement))
-            Library.addQueueItem(listElement.filename)
-        }
-
-        function appendList(items)
-        {
-            for (var i=0; i < items.length; i++) {
-                model.append(makeDict(items[i]))
-            }
-
-            Library.addQueueList(items)
-        }
-
-        function clear()
-        {
-            Library.clearQueue()
-            model.clear()
-
-            queueIndex = 0  // reset otherwise when you append and play 1 track it doesn't update correctly
-        }
-
-        // Optimised removeQueue for removing multiple tracks from the queue
-        function removeQueueList(items)
-        {
-            var i;
-
-            // Remove from the saved queue database
-            Library.removeQueueList(items)
-
-            // Sort the item indexes as loops below assume *numeric* sort
-            items.sort(function(a,b) { return a - b })
-
-            // Remove from the listmodel
-            var startCount = trackQueue.model.count
-
-            for (i=0; i < items.length; i++) {
-                // use diff in count as sometimes the row is removed from the model
-                trackQueue.model.remove(items[i] - (startCount - trackQueue.model.count));
-            }
-
-            // Update the currentIndex and playing status
-
-            if (trackQueue.model.count === 0) {
-                // Nothing in the queue so stop and pop the queue
-                player.stop()
-                mainPageStack.goBack()
-            } else if (items.indexOf(player.currentIndex) > -1) {
-                // Current track was removed
-
-                var newIndex;
-
-                // Find the first index that still exists before the currentIndex
-                for (i=player.currentIndex - 1; i > -1; i--) {
-                    if (items.indexOf(i) === -1) {
-                        break;
-                    }
-                }
-
-                newIndex = i;
-
-                // Shuffle index down as tracks were removed before the current
-                for (i=newIndex; i > -1; i--) {
-                    if (items.indexOf(i) !== -1) {
-                        newIndex--;
-                    }
-                }
-
-                // Set this as the current track
-                player.currentIndex = newIndex
-
-                // Play the next track
-                player.nextSong(player.isPlaying);
-            } else {
-                // Current track not in removed list
-                // Check if the index needs to be shuffled down due to removals
-
-                var before = 0
-
-                for (i=0; i < items.length; i++) {
-                    if (items[i] < player.currentIndex) {
-                        before++;
-                    }
-                }
-
-                // Update the index
-                player.currentIndex -= before;
-            }
-
-            queueIndex = player.currentIndex;  // ensure saved index is up to date
-        }
-    }
-
     // TODO: list of playlists move to U1DB
     // create the listmodel to use for playlists
     LibraryListModel {

=== modified file 'app/ui/AddToPlaylist.qml'
--- app/ui/AddToPlaylist.qml	2016-01-12 00:30:08 +0000
+++ app/ui/AddToPlaylist.qml	2016-01-15 18:03:59 +0000
@@ -17,7 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-import QtMultimedia 5.0
 import QtQuick 2.4
 import Ubuntu.Components 1.3
 import QtQuick.LocalStorage 2.0

=== modified file 'app/ui/NowPlaying.qml'
--- app/ui/NowPlaying.qml	2015-11-05 01:16:43 +0000
+++ app/ui/NowPlaying.qml	2016-01-15 18:03:59 +0000
@@ -61,12 +61,12 @@
 
     // Ensure that the listview has loaded before attempting to positionAt
     function ensureListViewLoaded() {
-        if (queueListLoader.item.count === trackQueue.model.count) {
-            positionAt(player.currentIndex);
+        if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
+            positionAt(player.mediaPlayer.playlist.currentIndex);
         } else {
             queueListLoader.item.onCountChanged.connect(function() {
-                if (queueListLoader.item.count === trackQueue.model.count) {
-                    positionAt(player.currentIndex);
+                if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
+                    positionAt(player.mediaPlayer.playlist.currentIndex);
                 }
             })
         }
@@ -99,29 +99,30 @@
             name: "default"
             actions: [
                 Action {
-                    enabled: trackQueue.model.count > 0
+                    enabled: !player.mediaPlayer.playlist.empty
                     iconName: "add-to-playlist"
                     // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
                     text: i18n.tr("Add to playlist")
+                    visible: !isListView
+
                     onTriggered: {
                         var items = []
 
-                        items.push(makeDict(trackQueue.model.get(player.currentIndex)));
+                        items.push(makeDict(player.metaForSource(player.mediaPlayer.playlist.currentItemSource)));
 
                         mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
                                            {"chosenElements": items})
                     }
                 },
                 Action {
-                    enabled: trackQueue.model.count > 0
+                    enabled: !player.mediaPlayer.playlist.empty
                     iconName: "delete"
                     objectName: "clearQueue"
                     // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
                     text: i18n.tr("Clear queue")
-                    onTriggered: {
-                        mainPageStack.goBack()
-                        trackQueue.clear()
-                    }
+                    visible: isListView
+
+                    onTriggered: player.mediaPlayer.playlist.clearWrapper()
                 }
             ]
             PropertyChanges {
@@ -140,13 +141,14 @@
                 // Remove the tracks from the queue
                 // Use slice() to copy the list
                 // so that the indexes don't change as they are removed
-                trackQueue.removeQueueList(selectedIndices.slice())
+                player.mediaPlayer.playlist.removeItemsWrapper(selectedIndices.slice());
             }
         }
     ]
 
     Loader {
         anchors {
+            bottom: nowPlayingToolbarLoader.top
             left: parent.left
             right: parent.right
             top: parent.top
@@ -155,7 +157,6 @@
 
         property real headerHeight: units.gu(10.125) // FIXME: 10.125 is the header.height with the page sections
 
-        height: parent.height - headerHeight - units.gu(9.5)
         source: "../components/NowPlayingFullView.qml"
         visible: !isListView
     }
@@ -163,8 +164,11 @@
     Loader {
         id: queueListLoader
         anchors {
-            bottomMargin: nowPlayingToolbarLoader.height + units.gu(2)
-            fill: parent
+            bottom: nowPlayingToolbarLoader.top
+            bottomMargin: units.gu(2)
+            left: parent.left
+            right: parent.right
+            top: parent.top
             topMargin: units.gu(2)
         }
         asynchronous: true
@@ -182,4 +186,13 @@
         height: units.gu(10)
         source: "../components/NowPlayingToolbar.qml"
     }
+
+    Connections {
+        target: player.mediaPlayer.playlist
+        onEmptyChanged: {
+            if (player.mediaPlayer.playlist.empty) {
+                mainPageStack.goBack()
+            }
+        }
+    }
 }

=== modified file 'app/ui/Playlists.qml'
--- app/ui/Playlists.qml	2016-01-12 00:30:08 +0000
+++ app/ui/Playlists.qml	2016-01-15 18:03:59 +0000
@@ -20,7 +20,6 @@
 
 import QtQuick 2.4
 import Ubuntu.Components 1.3
-import QtMultimedia 5.0
 import QtQuick.LocalStorage 2.0
 import "../logic/playlists.js" as Playlists
 import "../components"

=== modified file 'app/ui/Recent.qml'
--- app/ui/Recent.qml	2016-01-12 00:30:08 +0000
+++ app/ui/Recent.qml	2016-01-15 18:03:59 +0000
@@ -21,7 +21,6 @@
 import Ubuntu.Components 1.3
 import Ubuntu.MediaScanner 0.1
 import Ubuntu.Thumbnailer 0.1
-import QtMultimedia 5.0
 import QtQuick.LocalStorage 2.0
 import "../logic/meta-database.js" as Library
 import "../logic/playlists.js" as Playlists

=== modified file 'app/ui/Songs.qml'
--- app/ui/Songs.qml	2016-01-09 12:49:43 +0000
+++ app/ui/Songs.qml	2016-01-15 18:03:59 +0000
@@ -21,7 +21,6 @@
 import Ubuntu.Components 1.3
 import Ubuntu.MediaScanner 0.1
 import Ubuntu.Thumbnailer 0.1
-import QtMultimedia 5.0
 import QtQuick.LocalStorage 2.0
 import "../logic/playlists.js" as Playlists
 import "../components"
@@ -107,8 +106,8 @@
 
             onItemClicked: {
                 if (songsPage.state === "search") {  // only play single track when searching
-                    trackQueue.clear()
-                    trackQueue.append(songsModelFilter.get(index))
+                    player.mediaPlayer.playlist.clearWrapper();
+                    player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(songsModelFilter.get(index).filename));
                     trackQueueClick(0)
                 } else {
                     trackClicked(songsModelFilter, index)  // play track

=== modified file 'debian/changelog'
--- debian/changelog	2016-01-12 00:26:51 +0000
+++ debian/changelog	2016-01-15 18:03:59 +0000
@@ -1,9 +1,12 @@
 music-app (2.3) UNRELEASED; urgency=medium
-  
+
   [ Andrew Hayzen ]
   * Release 2.2ubuntu2 and start on 2.3
   * Use ListItemLayout for listitems to improve performance and match design guidelines (LP: #1526247).
   * Switch to using Qt GridView instead of custom CardView
+  * Add support for media-hub background playlists
+  * Bump framework to 15.04.3-qml
+  * Bump QtMultimedia import to 5.6
 
   [ Ken VanDine ]
   * Install the content-hub json file in the correct place for peer registry

=== modified file 'manifest.json.in'
--- manifest.json.in	2015-12-03 14:14:14 +0000
+++ manifest.json.in	2016-01-15 18:03:59 +0000
@@ -1,7 +1,7 @@
 {
     "architecture": "all",
     "description": "A music application for ubuntu",
-    "framework": "ubuntu-sdk-15.04.1-qml",
+    "framework": "ubuntu-sdk-15.04.3-qml",
     "hooks": {
         "music": {
             "apparmor": "apparmor.json",

=== modified file 'tests/autopilot/music_app/__init__.py'
--- tests/autopilot/music_app/__init__.py	2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/__init__.py	2016-01-15 18:03:59 +0000
@@ -54,7 +54,6 @@
         # Use only objectName due to bug 1350532 as it is MainView12
         self.main_view = self.app.wait_select_single(
             objectName="musicMainView")
-        self.player = self.app.select_single(Player, objectName='player')
 
     def get_add_to_playlist_page(self):
         return self.app.wait_select_single(AddToPlaylist,
@@ -95,8 +94,7 @@
             Playlists, objectName='playlistsPage')
 
     def get_queue_count(self):
-        return self.main_view.select_single("LibraryListModel",
-                                            objectName="trackQueue").count
+        return self.player.count
 
     def get_songs_view(self):
         return self.app.wait_select_single(SongsView, objectName="songsPage")
@@ -121,6 +119,11 @@
                 objectName="LoadingSpinner").running and
                 self.main_view.select_single("*", "allSongsModel").populated)
 
+    @property
+    def player(self):
+        # Get new player each time as data changes (eg currentMeta)
+        return self.app.select_single(Player, objectName='player')
+
     def populate_queue(self):
         tracksPage = self.get_songs_page()  # switch to track tab
 
@@ -263,15 +266,16 @@
 class Player(UbuntuUIToolkitCustomProxyObjectBase):
     """Autopilot helper for Player"""
 
+    @property
+    def currentMeta(self):
+        return self.select_single("*", objectName="currentMeta")
+
 
 class NowPlaying(MusicPage):
     """ Autopilot helper for now playing page """
     def __init__(self, *args):
         super(NowPlaying, self).__init__(*args)
 
-        root = self.get_root_instance()
-        self.player = root.select_single(Player, objectName="player")
-
         self.visible.wait_for(True)
 
     @ensure_now_playing_full
@@ -279,6 +283,9 @@
     def click_forward_button(self):
         return self.wait_select_single("*", objectName="forwardShape")
 
+    def click_full_view(self):
+        self.main_view.get_header().switch_to_section_by_index(0)
+
     @ensure_now_playing_full
     @click_object
     def click_play_button(self):
@@ -289,6 +296,9 @@
     def click_previous_button(self):
         return self.wait_select_single("*", objectName="previousShape")
 
+    def click_queue_view(self):
+        self.main_view.get_header().switch_to_section_by_index(1)
+
     @ensure_now_playing_full
     @click_object
     def click_repeat_button(self):
@@ -299,17 +309,21 @@
     def click_shuffle_button(self):
         return self.wait_select_single("*", objectName="shuffleShape")
 
-    def click_full_view(self):
-        self.main_view.get_header().switch_to_section_by_index(0)
-
-    def click_queue_view(self):
-        self.main_view.get_header().switch_to_section_by_index(1)
+    @click_object
+    def click_track(self, i):
+        return self.get_track(i)
 
     @ensure_now_playing_list
     def get_track(self, i):
         return (self.wait_select_single(MusicListItem,
                 objectName="nowPlayingListItem" + str(i)))
 
+    @property
+    def player(self):
+        # Get new player each time as data changes (eg currentMeta)
+        root = self.get_root_instance()
+        return root.select_single(Player, objectName="player")
+
     @ensure_now_playing_full
     def seek_to(self, percentage):
         progress_bar = self.wait_select_single(

=== modified file 'tests/autopilot/music_app/tests/test_music.py'
--- tests/autopilot/music_app/tests/test_music.py	2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/tests/test_music.py	2016-01-15 18:03:59 +0000
@@ -77,9 +77,9 @@
         self.app.populate_queue()  # populate queue
 
         # Check current meta data is correct
-        self.assertThat(self.player.currentMetaTitle,
+        self.assertThat(self.player.currentMeta.title,
                         Eventually(Equals(self.tracks[0]["title"])))
-        self.assertThat(self.player.currentMetaArtist,
+        self.assertThat(self.player.currentMeta.author,
                         Eventually(Equals(self.tracks[0]["artist"])))
 
     def test_play_pause_library(self):
@@ -159,12 +159,12 @@
         now_playing_page = self.app.get_now_playing_page()
 
         # save original song data for later
-        org_title = self.player.currentMetaTitle
-        org_artist = self.player.currentMetaArtist
+        orig_title = self.player.currentMeta.title
+        orig_artist = self.player.currentMeta.author
 
         # check original track
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
-        logger.debug("Original Song %s, %s" % (org_title, org_artist))
+        logger.debug("Original Song %s, %s" % (orig_title, orig_artist))
 
         # select pause and check the player has stopped
         now_playing_page.click_play_button()
@@ -174,6 +174,10 @@
 
         # goal is to go back and forth and ensure 2 different songs
         now_playing_page.click_forward_button()
+
+        # MediaPlayer Playlist does not auto play after next/previous
+        now_playing_page.click_play_button()
+
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
 
         # select pause and check the player has stopped
@@ -181,18 +185,22 @@
         self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
 
         # ensure different song
-        self.assertThat(self.player.currentMetaTitle,
-                        Eventually(NotEquals(org_title)))
-        self.assertThat(self.player.currentMetaArtist,
-                        Eventually(NotEquals(org_artist)))
+        self.assertThat(self.player.currentMeta.title,
+                        Eventually(NotEquals(orig_title)))
+        self.assertThat(self.player.currentMeta.author,
+                        Eventually(NotEquals(orig_artist)))
 
-        logger.debug("Next Song %s, %s" % (self.player.currentMetaTitle,
-                                           self.player.currentMetaArtist))
+        logger.debug("Next Song %s, %s" % (self.player.currentMeta.title,
+                                           self.player.currentMeta.author))
 
         now_playing_page.seek_to(0)  # seek to 0 (start)
 
         # select previous and ensure the track is playing
         now_playing_page.click_previous_button()
+
+        # MediaPlayer Playlist does not auto play after next/previous
+        now_playing_page.click_play_button()
+
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
 
         # select pause and check the player has stopped
@@ -200,10 +208,10 @@
         self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
 
         # ensure we're back to original song
-        self.assertThat(self.player.currentMetaTitle,
-                        Eventually(Equals(org_title)))
-        self.assertThat(self.player.currentMetaArtist,
-                        Eventually(Equals(org_artist)))
+        self.assertThat(self.player.currentMeta.title,
+                        Eventually(Equals(orig_title)))
+        self.assertThat(self.player.currentMeta.author,
+                        Eventually(Equals(orig_artist)))
 
     def test_mp3(self):
         """ Test that mp3 "plays" or at least doesn't crash on load """
@@ -231,7 +239,7 @@
                         Eventually(Equals(initial_tracks_count + 1)))
 
         # Ensure the current track is mp3
-        self.assertThat(self.player.source.endswith("mp3"),
+        self.assertThat(self.player.currentItemSource.endswith("mp3"),
                         Equals(True))
 
         # Start playing the track (click from toolbar)
@@ -244,9 +252,9 @@
         toolbar.click_play_button()
 
         # Check current meta data is correct
-        self.assertThat(self.player.currentMetaTitle,
+        self.assertThat(self.player.currentMeta.title,
                         Eventually(Equals(self.tracks[i]["title"])))
-        self.assertThat(self.player.currentMetaArtist,
+        self.assertThat(self.player.currentMeta.author,
                         Eventually(Equals(self.tracks[i]["artist"])))
 
     def test_shuffle(self):
@@ -258,30 +266,31 @@
 
         now_playing_page = self.app.get_now_playing_page()
 
+        now_playing_page.set_repeat(True)
+        now_playing_page.set_shuffle(True)
+
         # pause the track if it is playing
         if self.player.isPlaying:
             now_playing_page.click_play_button()
 
         self.player.isPlaying.wait_for(False)
 
-        now_playing_page.set_shuffle(True)  # enable shuffle
-
-        # save original song metadata
-        org_title = self.player.currentMetaTitle
-        org_artist = self.player.currentMetaArtist
-
-        logger.debug("Original Song %s, %s" % (org_title, org_artist))
-
         count = 0
+        previous_index = -1
 
-        # loop while the track is the same if different then a shuffle occurred
-        while (org_title == self.player.currentMetaTitle and
-               org_artist == self.player.currentMetaArtist):
+        # Keep going until the index is not previous + 1 (with wrapping)
+        # or previous == currentIndex (to ensure shuffle is working)
+        while ((previous_index + 1) % self.player.count ==
+               self.player.currentIndex or
+               previous_index == self.player.currentIndex):
             logger.debug("count %s" % (count))
 
             # check count is valid
             self.assertThat(count, LessThan(100))
 
+            # store this index as the previous
+            previous_index = self.player.currentIndex
+
             # select next track
             now_playing_page.click_forward_button()
 
@@ -289,28 +298,13 @@
             if self.player.isPlaying:
                 now_playing_page.click_play_button()
 
-            # check it is paused
-            self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
-
-            # save current file so we can check it goes back
-            source = self.player.currentMetaFile
-
-            # select previous track while will break if this previous track
-            # is different and therefore a shuffle has occurred
-            now_playing_page.click_previous_button()
-
-            # pause the track if it is playing
-            if self.player.isPlaying:
-                now_playing_page.click_play_button()
-
-            # check it is paused
-            self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
-
-            # check the file has actually changed
-            self.assertThat(self.player.currentMetaFile,
-                            Eventually(NotEquals(source)))
-
-            count += 1  # increment count
+            self.player.isPlaying.wait_for(False)
+
+            # toggle shuffle to increase random
+            now_playing_page.set_shuffle(False)
+            now_playing_page.set_shuffle(True)
+
+            count += 1
 
     def test_show_albums_page(self):
         """tests navigating to the Albums tab and displaying the album page"""
@@ -648,7 +642,7 @@
             now_playing_page.click_forward_button()
 
         # Make sure we loop back to first song after last song ends
-        self.assertThat(self.player.currentMetaTitle,
+        self.assertThat(self.player.currentMeta.title,
                         Eventually(Equals(self.tracks[0]["title"])))
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
 
@@ -667,7 +661,7 @@
             now_playing_page.click_forward_button()
 
         # Make sure we loop back to first song after last song ends
-        self.assertThat(self.player.currentMetaTitle,
+        self.assertThat(self.player.currentMeta.title,
                         Eventually(Equals(self.tracks[0]["title"])))
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
 
@@ -681,16 +675,16 @@
         now_playing_page.set_shuffle(False)
         now_playing_page.set_repeat(True)
 
-        initial_song = self.player.currentMetaTitle
+        initial_song = self.player.currentMeta.title
         now_playing_page.click_previous_button()
 
         # If we're far enough into a song, pressing prev just takes us to the
         # beginning of that track.  In that case, hit prev again to actually
         # skip over the track.
-        if self.player.currentMetaTitle == initial_song:
+        if self.player.currentMeta.title == initial_song:
             now_playing_page.click_previous_button()
 
-        self.assertThat(self.player.currentMetaTitle,
+        self.assertThat(self.player.currentMeta.title,
                         Eventually(Equals(self.tracks[-1]["title"])))
         self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
 
@@ -702,19 +696,23 @@
         now_playing_page = self.app.get_now_playing_page()
 
         self.player.isPlaying.wait_for(True)  # ensure the track is playing
-        self.player.position.wait_for(GreaterThan(5000))  # wait until > 5s
+
+        # wait until > 5s
+        self.player.position.wait_for(GreaterThan(5000))
 
         now_playing_page.click_play_button()  # pause the track
         self.player.isPlaying.wait_for(False)  # ensure the track has paused
 
-        source = self.player.source  # store current source
+        source = self.player.currentMeta.filename  # store current source
 
         now_playing_page.click_previous_button()  # click previous
 
         # resume the track (to ensure position updates)
         now_playing_page.click_play_button()
 
-        self.player.position.wait_for(LessThan(5000))  # wait until < 5s
+        # wait until < 5s
+        self.player.position.wait_for(LessThan(5000))
 
         # Check that the source is the same
-        self.assertThat(self.player.source, Eventually(Equals(source)))
+        self.assertThat(self.player.currentMeta.filename,
+                        Eventually(Equals(source)))


Follow ups